js的基本概念详解

时间:2023-03-09 14:15:36
js的基本概念详解

  来自《javascript高级程序设计 第三版:作者Nicholas C. Zakas》的学习笔记(三)

  

  如果你刚学js,想快速了解到js的基本概念,以下将会是一篇不错的引导文章:

  • 语法
  • 数据类型
  • 流控制语句
  • 理解函数

  首先讲个段子:曾经有个人很天真地以为javascript和java肯定是一家,因为学过java,觉得javascript学习肯定就是三下五除二了。了解了一些javascript语法后,直到看过一遍淘宝前端团队译的《Javascript权威指南》,大为惊叹,刹那间愣住了(不得不提一下,如果想学习javascript,我首推Nicholas C. Zakas的《javascript高级程序设计》,如果有任何语言(稍微夸张一下^_^)的编程基础,《Javascript权威指南》我觉得更适合拿来做手册或随时翻阅。当然两本书都是经典之作)。当然,it's me^_^,目前正在系统学习js。

  一、语法部分:

  1. ECMAScript区分大小写
  2. ECMAScript标识符采用驼峰式大小写格式
  3. 注释用  //  或  /*.............*/
  4. ECMAScript的严格模式:可以参看资料http://www.jb51.net/article/33619.htm
    function doSomething() {
    "use strict";
    //函数体
    }

    “use strict”是一个编译指示,用于告诉支持的Javascript引擎切换到严格模式。

  5. 好的编程习惯:要注意代码规范,语句结尾的分号虽然不是必需的,但是加上分号也会在某些情况下增进代码的性能,因为这样解析器就不必再花时间推测应该在哪里插入分号了。在控制语句中要使用代码块,即使代码块中只有一条语句,如
    if (test) {
    alert(test);
    }

    保证代码意图更加清晰。

  6. ECMAScript的变量是松散类型的

  • 使用var操作符定义的变量将成为该变量的作用域中的局部变量。也就是说,如果在函数中使用var定义一个变量,那么这个变量在函数退出后就会被销毁,如:
    function test() {
    var message = "hi"; //局部变量
    }
    test();
    alert(message); //错误
  • 可以这样省略var
    function test() {
    message = "hi"; //全局变量
    }
    test();
    alert(message); //"hi"

    但是,在严格模式下,给未经声明的变量赋值会导致抛出ReferenceError错误。

  • var message = "hi",found = false,age = 29;因为是松散性,所以这样使用不同类型初始化变量的操作是合法的。在严格模式下不能定义名为eval和arguments的变量。

  7. ECMAScript中有5种简单的数据类型:Undefined、Null、Boolean、Number和String。还有1种复杂数据类型——Object。你可能会问,这么几个数据类型就是enougn了?事实上,由于ECMAScript数据类型具有动态性,的确没有必要再定义其他数据类型的必要了。

  8. 给定变量的数据类型。有时候,typeof操作符会返回一些令人迷惑但技术上却正确的值,如,调用typeof null会返回"object",因为特殊值null被认为是一个空的对象引 用。Safari5及之前版本、chrome7及之前的版本在对正则表达式调用typeof操作符时会返回"function",而其他浏览器在这种情况下会返回object(对此me没有验证过)。

  9. 在使用var声明变量但未对其初始化时,这个变量的值就是undefined,例如:

  • var message;
    alert(message == undefined); //true

不过,包含undefined值的变量与尚未定义的变量还是不一样的。看如下例子:

  • var message; //这个变量声明之后默认值取undefined值
    
    //下面这个变量并没有声明
    
    //var age
    
    alert(message); //"undefined"
    
    alert(age); //产生错误
    
    alert(typeof message); //"undefined"
    
    alert(typeof age); //"undefined"即对未声明的变量执行typeof操作也返回undefined

  10. 如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为null而不是其它值。这样只要直接检查null就可以知道相应的变量是否已经保存了一个对象的引用:

  • if (car != null) {
    //对car对象执行某些操作
    }

    null和undefined有如下关系:

    alert(null == undefined);   //true

    但是两者用途完全不同:任何情况下都没有必要显示设置变量值为undefined,但是null不是。只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存null值。

  11. Boolean类型的字面值true和false是区分大小写的。即True和False并不是Boolean值。Boolean(参数)可以将参数转换为Boolean值。对各种数据类型有相应的转换规    则:

  • 数据类型 转换为true 转换为false值
    Boolean true false
    String 任何非空字符串 ""(空字符串)
    Number 任何非零数字值(包括无穷大) 0和NaN
    Object 任何对象 null
    Undefined n/a undefined

  

  掌握这些规则后,对理解控制语句很有帮助,如:

  • var message = "hello world";
    if (message) {
    alert("value is true");
    }

    12. Number类型,八进制字面值的第一位必须是零,十六进制字面值的前两位必须是0X:

   小记:鉴于javascript中保存数值的方式,可以保证正零(+0)和(-0)。正零和负数被认为相等。

13. 浮点数:

  • var floatNum1 = 1.     //小数点后面没有数字
    var floatNum2 = 10.0 //整数——解析为10 var floatNum = 3.125e7 //等于31250000 /*由于浮点数值的最高精度是17位小数,但在进行算术计算的时候其精度远远不如整数。如,0.1+0.2的结果不是0.3,而是0.300 000 000 000 000 04 。这个小小的舍入误差会导致无法测试待定的浮点数值*/ if (a + b == 0.3) { //不要做这样的测试
    alert("you got 0.3");
    }

    guys想过没有,为什么会有舍入误差呢?作者Nicholas并没有在书中正式给出详细解释,只是点到“使用基于IEEE754数值的浮点计算的通病”。

  • 小数的二进制表示问题

    如 0.5=1*2^-1

    任意浮点数y=x1*2^(-1) +x2*2^(-2) + x3*2^(-3).....

    0.5这个浮点数可以被准确表示,但是基于如上的二进制表示方式,对于部分浮点数是没办法准确表示的,比如 0.3 ,0.6  它们以非常接近的近似值表示,所以实际使用中会遇到意外的情况。

  • 以下为转载部分:
  • 1.
    在讨论浮点数之前,先看一下整数在计算机内部是怎样表示的。
      int num=9;
    上面这条命令,声明了一个整数变量,类型为int,值为9(二进制写法为1001)。普通的32位计算机,用4个字节表示int变量,所以9就被保存为00000000 00000000 00000000 00001001,写成16进制就是0x00000009。
    那么,我们的问题就简化成:为什么0x00000009还原成浮点数,就成了0.000000?
    2.
    根据国际标准IEEE 754,任意一个二进制浮点数V可以表示成下面的形式:
      
      (1)(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
      (2)M表示有效数字,大于等于1,小于2。
      (3)2^E表示指数位。
    举例来说,十进制的5.0,写成二进制是101.0,相当于1.01×2^2。那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。
    十进制的-5.0,写成二进制是-101.0,相当于-1.01×2^2。那么,s=1,M=1.01,E=2。
    IEEE 754规定,对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。 对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
    ~~~二进制形式 1位符号位S+8位指数位E(阶码)+23位有效数字M
    3.
    IEEE 754对有效数字M和指数E,还有一些特别规定。
    前面说过,1≤M<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
    至于指数E,情况就比较复杂。
    首先,E为一个无符号整数(unsigned int)。这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,E的真实值必须再减去一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。 ~~~由于E是无符号数 而指数可以为负数,所以用127这个参考值来判断正负指数值 指数表示值=真实值+127
    比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
    然后,指数E还可以再分成三种情况:
    (1)E不全为0或不全为1。这时,浮点数就采用上面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
    (2)E全为0。这时,浮点数的指数E等于1-127(或者1-1023),有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
    (3)E全为1。这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。
    4.
    为什么0x00000009还原成浮点数,就成了0.000000?
    首先,将0x00000009拆分,得到第一位符号位s=0,后面8位的指数E=00000000,最后23位的有效数字M=000 0000 0000 0000 0000 1001。
    由于指数E全为0,所以符合上一节的第二种情况。因此,浮点数V就写成:
      V=(-1)^0×0.00000000000000000001001×2^(-126)=1.001×2^(-146)
    显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000。
    5.
    请问浮点数9.0,如何用二进制表示?还原成十进制又是多少?
    首先,浮点数9.0等于二进制的1001.0,即1.001×2^3。
    那么,第一位的符号位s=0,有效数字M等于001后面再加20个0,凑满23位,指数E等于3+127=130,即10000010。
    所以,写成二进制形式,应该是s+E+M,即0 10000010 001 0000 0000 0000 0000 0000。这个32位的二进制数,还原成十进制,正是1091567616。

    14.确定一个数是否是有穷的,即是不是位于最大和最小的数值之间(由于内存的限制,Number是有数值范围的),可以使用isFinite()函数;

   15.NaN

  • 非数值,任何数值除以0会返回NaN
  • 任何涉及NaN的操作都会返回NaN,NaN与任何值都是不相等,包括NaN本身,定义有isNaN()函数。isNaN()函数就是检测传入参数是不是可以直接转换成数值;
  • 基于对象调用isNaN()函数,会首先调用对象的valueOf()方法,然后确定该方法返回的值是不是可以转换为数值。如果不能,则基于这个返回值再调用toString()方法测试返回值

    16. 有三个函数可以把非数值转换为数值:Number()、parseInt()、parseFloat()。相应的转换规则可以查阅资料。

   17. 有关object的实例都具有的属性和方法。object就是数据和功能的集合。

  • constructor保存着用于创建当前对象的函数
  • hasOwnProperty(propertyName)用于检查给定的属性是否在当前对象实例中存在(注意:是当前对象的实例,而不是在实例原型中)
  • isPropertyOf(object)检查传入的对象是否是另一个对象的原型
  • 还有propertyIsEnumerable(propertyName)、toLocalString()、toString()、valueOf(),这些理解起来就比较容易了

   18. 有关操作符,我们可以留意下递增递减操作符遵循的规则。

    以下是其它部分示例,可以从代码总结到很多(我不再指出,考考你的眼力^_^)

  • var num = 25;
    num = +num; //仍然是25
    var num = 25;
    num = -num; //变成了-25

    18. 位操作不再详细介绍(如果你是计算机专业的,多少课程涉及过这个,I believe you know~)。只提几点:

  • 对特殊的NaN和Infinity值应用位操作时,这两个值都会被当成0来处理

  • 无符号右移操作符会把负数的二进制当成正数的二进制。而且负数以其绝对值的二进制补码形式出现,因此就会导致无符号右移后的结果非常之大

   19. 逻辑非操作

  • 操作数是一个对象//非空字符串//任意非0数值//,返回false
  • 操作数是一个空字符串//数值0//null//NaN//undefined,返回true

     逻辑与操作(是一个短路操作)

  • 如果第一个操作数是对象,则返回第二个操作数
  • 如果第二个操作数是对象,而只有在第一个操作数的求值结果为true时才会返回该对象
  • 如果两个操作数都是对象,则返回第二个操作数
  • 如果有一个操作数是null,则返回null
  • 如果有一个操作数是NaN,则返回NaN
  • 如果有一个操作数是undefined,则返回undefined

        逻辑或与逻辑与很相似,如果一个操作数不是布尔值,逻辑或也不一定返回布尔值,并且也是短路运算。

  • 如果第一个操作数是对象,则返回第一个操作数
  • 如果第一个操作数的求值结果为false,则返回第二个操作数
  • 如果两个操作数都是对象,则返回第一个操作数
  • 如果两个操作数都是null返回null,都是NaN返回NaN,都是undefined返回undefined,利用这一点可以写这么一个语句避免为变量赋null或undefined:
  • var myObject = preferredObject || backupObject

       乘法操作符:

  • 常规乘法计算
  • NaN*任何数 = NaN
  • Infinity*0 = NaN
  • Infinity*非0数值 = Infinity或-Infinity
  • Infinity*Infinity = Infinity
  • 如果有一个数不是数值,则后台调用Number()将其转换---(在这里,你就需要明白Number()的转换规则,环环相扣),然后再饮用上述规则

      除法

  • 常规除法
  • 有一个数是NaN,结果是NaN
  • Infinity/Infinity = NaN
  • 0/0 = NaN
  • 非零的有限数/0 = Infinity或-Infinity
  • 如果有一个操作数不是数值,这后台调用Number(),再应用上述规则

      求模(你可能会发现,有些规则是有交集的,比如 0 % 0的值,动动手,写个代码试试^_^)

  • 常规操作
  • 无穷大的被除数%有限大的除数 = NaN
  • 有限大的被除数%0 = NaN
  • Infinity%Infinity = NaN
  • 有限大的被除数%无穷大的除数 = 被除数
  • 被除数是0,这结果是0
  • 如果有一个操作数不是数值,则后台调用Number()再进行规则应用  

   相应的还有加法,需要注意的是:只要其中有一个操作数是字符串,则另一个操作数也会装换为字符串,然后进行字符串的拼接

  • 有一个数是NaN,结果是NaN
  • Infinity + Infinity = Infinity
  • (-Infinity) + (-Infinity) = (-Infinity)
  • (+Infinity) + (-Infinity) = NaN
  • (+0) + (+0) = (+0)
  • (-0) + (-0) = (-0)
  • (+0) + (-0) = (+0)
  • 如果两个操作数都是字符串,则进行拼接;如果只有一个是字符串,则将另一个数转化为字符串进行拼接
  • 若对于对象、数值、布尔值则调用toString()方法取得相应的字符串值再应用规则。如果是undefined和null,用toString可以取得为"undefined"和"null"
  • var num1 = 5;
    var num2 = 10;
    var message = "The sum of 5 and 10 is " + num1 + num2;
    alert(message); //“The sum of 5 and 10 is 510

    还有减法操作的规则。。。。。有点晕有没有,其实你应该是能发现规律滴,。,。,。,。

  • 常规减法
  • 有一个数是NaN,为NaN
  • Infinity - Infinity = NaN
  • (-Infinity) - (-Infinity) = NaN
  • Infinity - (-Infinity) = Infinity
  • -Infinity - Infinity = Infinity
  • (+0) - (+0) = (+0)
  • (+0) - (-0) = (-0)
  • (-0) - (-0) = (+0)
  • 对于字符串和对象等的处理同加法。。。

   关系操作数,注意:在比较字符串时,实际比较的是两个字符串的对应位置的每个字符的字符编码。由于大写字母的字符编码全部小于小写字母的字符编码,所以有:

  • var result = "Block" < "alphabet"   //true

    另外一种现象是:

  • var result = "23" < "3"    //true           2的编码是50   3的编码是51
    var result = "23" < 3  //true    数值和字符串进行比较,字符串被转换为数值
    var reault = "a" < 3     //false,因为"a"被转换为NaN
    var result1 = NaN < 3    //false
    var result2 = NaN >= 3 //false 任何数与NaN的比较都返回false

       相等操作符的注意事项。。。。特别留意全等和不全等:

  • var result1 = ("55" == 55);     //true,因为转换后相等
    var result2 = ("55" === 55); //false,因为不同的数据类型不相等
    null == undefined   //true  因为它们是类似的值
    null ===undefined //false 因为它们是不同类型的

    20. 和其它语言一样还有:条件操作符、赋值操作符、逗号操作符、if语句、do-while语句、while语句、for语句、for-in语句、label语句、break和continues语句、with语句(严格模式下不允许使用with语句,而且大量使用with语句会出现性能下降问题,所以不建议使用)、switch语句

  • 强调一下逗号操作符:逗号操作符用于声明多个变量;var num1 = 1,num2 = 2,num3 =3; 除此之外,还可用于赋值,但是总会返回表达式中最后一项
  • var num = (5,1,4,8,0);  //num 的值为0

    21.ECMAScript函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型,也就是说即便你定义的函数只接受两个函数,也未必在调用的时候一定要传两个参数进来。原因是:ECMAScript中的参数在内部是用一个数组来表示。在函数体内可以通过arguments对象来访问这个参数数组。但是argument只是与数组类似,并不是说就是Array的实例。

  • function doAdd() {
    if (arguments.length == 1) {
    alert(arguments[0] + 10);
    } else if (arguments.length == 2) {
    alert(arguments[0] + arguments[1]);
    }
    } doAdd(10); //
    doAdd(30,20); //50 少年 有没有想到C++中的重载

    还有一点有意思,arguments的值永远与对应的命名参数的值保持同步。

  • function doAdd(num1,num2) {
    arguments[1] = 10;
    alert(arguments[0] + num2); //其中num2因为有arguments[1]=10而被赋值为10,但是这并不是说,读取这两个值会访问相同的内存空间,它们的内存空间是独立的,但  是它们的值同步,但是影响是单向,即修改命名参数的值不会影响arguments的值。另外记住:没有传递值的命名参数将被自动赋予undefined
    }

    函数没有重载,当定义两个相同的函数,则改名字只属于后定义的函数。如果剖根究底地问,为什么没有重载?书本上的回答是:因为没有函数签名。函数签名是什么?囧~

   google下得到这个答案:方法签名由方法的名称和它的每一个形参(按从左到右的顺序)的类型和种类(值、引用或输出)组成。需注意的是,方法签名既不包含返回类型,   也不包含 params 修饰符(它可用于最右边的参数)。个人觉得,这个解析未必完全正确,但是我们可以得到信息,函数签名应该是和参数类型、返回类型等有关,而从上   面的解析arguments的用法,函数参数的不确定性中,联系到C++等语言中函数重载的实际意义(函数重载就是:函数名相同、参数个数或类型或返回值类型的不同函数)   知道js为什么无法实现函数重载了^_^。