js的基本概念详解
来自《javascript高级程序设计 第三版:作者Nicholas C. Zakas》的学习笔记(三)
如果你刚学js,想快速了解到js的基本概念,以下将会是一篇不错的引导文章:
- 语法
- 数据类型
- 流控制语句
- 理解函数
首先讲个段子:曾经有个人很天真地以为javascript和java肯定是一家,因为学过java,觉得javascript学习肯定就是三下五除二了。了解了一些javascript语法后,直到看过一遍淘宝前端团队译的《Javascript权威指南》,大为惊叹,刹那间愣住了(不得不提一下,如果想学习javascript,我首推Nicholas C. Zakas的《javascript高级程序设计》,如果有任何语言(稍微夸张一下^_^)的编程基础,《Javascript权威指南》我觉得更适合拿来做手册或随时翻阅。当然两本书都是经典之作)。当然,it's me^_^,目前正在系统学习js。
一、语法部分:
- ECMAScript区分大小写
- ECMAScript标识符采用驼峰式大小写格式
- 注释用 // 或 /*.............*/
- ECMAScript的严格模式:可以参看资料http://www.jb51.net/article/33619.htm
function doSomething() { "use strict"; //函数体 }
“use strict”是一个编译指示,用于告诉支持的Javascript引擎切换到严格模式。
- 好的编程习惯:要注意代码规范,语句结尾的分号虽然不是必需的,但是加上分号也会在某些情况下增进代码的性能,因为这样解析器就不必再花时间推测应该在哪里插入分号了。在控制语句中要使用代码块,即使代码块中只有一条语句,如
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); //20 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为什么无法实现函数重载了^_^。