《JavaScript高级程序设计》第三章,基本概念。
语法
区分大小写
ECMAScript中的一切都区分大小写,如变量名test和变量名Test表示两个不同变量。
严格模式
ECMAScript5引入了严格模式,它定义了一种不同的解析与执行模型,用以解决ECMAScript3中一些不确定或不安全行为的报错,使得调试错误变得更为直接。启用严格模式的方式为在作用域第一行加上'use strict';
,作为编译指示(pragma)。
语句
ECMAScript中的语句以分号结尾,若省略则由解析器确定语句的结尾。不建议省略,因为分号可以避免很多错误、方便直接进行代码压缩、增进代码性能。
变量
使用var操作符定义的变量将成为定义该变量的作用域中的局部变量,省略var操作符可以定义全局变量,但是不建议这样使用。
数据类型
ECMAScript中有5种简单数据类型(Undefined、Null、Boolean、Number、String)和1种复杂数据类型(Object)。
typeof操作符
typeof是操作符而非函数(也因此可以选择不对其操作对象加括号),对一个值使用typeof操作符可能返回下列某个字符串:
- “undefined”:当值未定义
- “boolean”:当值为布尔值
- “number”:当值为数值
- “string”:当值为字符串
- “object”:当值为对象或null
- “function”:当值为函数
Undefined类型
Undefined类型只有一个值:undefined
。声明了但未初始化的变量的值是undefined,未声明的变量不是;但若对二者使用typeof操作符均会返回”undefined”。
1 | var declaredVar; |
Null类型
Null类型只有一个值:null
。在逻辑上看null值指示一个空对象指针,对其使用typeof操作符会返回”object”。undefined值派生于null,因此null == undefined
的值为true
。
Boolean类型
Boolean类型只有两个值:true
、false
。这两个值与数字值无关,因此true不一定等于1,false不一定等于0。虽然Boolean类型字面值只有两个,但ECMAScript种所有类型的值都有与这两个Boolean值等价的值,若要转换可调用函数Boolean()
,转换规则如下:
- String:
- 任何非空字符串->true
- 空字符串->false
- Number:
- 任何非零数字包括无穷大->true
- 0和NaN->false
- Object:
- 任何对象->true
- null->false
- Undefined:
- undefined->false
Number类型
使用IEEE-754格式表示整数和浮点数,也因此会存在浮点数值计算的舍入误差问题(如0.1 + 0.2 != 0.3)。整数可表示为十进制、八进制(第一位为0)、十六进制(前两位为0x)。
数值范围
ECMAScript能够表示的最小/大数值保存于Number.MIN_VALUE
和Number.MAX_VALUE
中。超出数值范围的数将转化成特殊的-Infinity
或Infinity
,判断可用函数isFinite()
。
NaN
NaN用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会报错了),如任何数值除以0。NaN有两个特点:
- 任何涉及NaN的操作都会返回NaN
- NaN与任何值都不相等,包括NaN本身
判断一个参数是否”不是数值”可以用函数isNaN()
。
1 | alert(isNaN(NaN)); // true |
数值转换
有3个函数可以把非数值转换为数值:Number()
、parseInt()
、parseFloat()
。前者可用于任何数据类型,后两者专门用于字符串。
Number()
规则略多。
1 | // Boolean、数字都很简单 |
parseInt()
处理方式:忽略字符串前面空格,直到找到第一个非空个字符。如果第一个字符不是数字字符或负号就返回NaN,再继续解析第二个字符、第三个字符等等,直到解析完所有字符或者遇到一个非数字字符。
1 | parseInt("") // NaN |
parseFloat()
与parseInt()
类似,从第一个字符开始解析,直到解析完所有字符或者遇到一个无效的浮点数字字符。与parseInt()
不同之处在于:字符串中第一个.
有效,第二个.
无效;只解析为十进制。
1 | parseFloat("") // NaN |
String类型
特点
ECMAScript中的字符串是不可变的。如果要改变某个变量保存的字符串,首先要销毁原来的字符串,再用一个新字符串填入该变量。
转换为字符串
toString()
1 | // null和undefined没有toString()方法 |
String()
规则:
- 当值有
toString()
方法,则调用该方法并返回 - 当值为null,返回”null”
- 当值为undefined,返回”undefined”
其他方法
如果要把某个值转为字符串,可以令其加上空字符串(“”)。
Object类型
Object的每个实例都有下列属性和方法:
- Constructor
- hasOwnProperty(propertyName)
- isPrototypeOf(object)
- propertyIsEnumerable(propertyName)
- toLocaleString()
- toString()
- valueOf()
操作符
一元操作符
包括(++)、(–)、(+)、(-),常规用法不赘述,注意点在于:
- 应用于布尔值时,将false看成0、true看成1
- 应用于字符串时,如果是有效数字,则用该数字,否则返回NaN
- 应用于对象时,调用
valueOf()
方法得到值,如果结果是NaN则调用toString()
方法得到值
位操作符
ECMAScript中所有数值都以IEEE-754 64位格式存储,但位操作符不直接操作64位的值,而是先将其转换为32位整数,执行操作,最后转换回64位。
- 非(~)
- 与(&)
- 或(|)
- 异或(^)
- 左移(<<):符号位不动,空缺填0,不影响符号位
- 有符号右移(>>):符号位不动,空缺填符号位的值,不影响符号位
- 无符号右移(>>>):整体右移,空缺填0(负数会影响符号位)
布尔操作符
- 非(!)
- 与(&&)
逻辑与是短路操作符。除了常规操作,还存在当一个操作数不是布尔值时,逻辑与的返回值不一定是布尔值的情况,规则如下。- object && _ => _
- _ && object => object when _ is true
- object1 && object2 => object2
- return null/NaN/undefined if there exists null/NaN/undefined
- 或(||)
与逻辑与类似,规则如下。- object || _ => object
- _ && object => object when _ is false
- object1 || object2 => object1
- return null/NaN/undefined if both are null/NaN/undefined
乘性操作符
- 乘法
- return NaN if there exists NaN
- Infinity * 0 => NaN
- Infinity * non-zero-val => ± Infinity
- Infinity * Infinity => Infinity
- 除法
- return NaN if there exists NaN
- 0 / 0 => NaN
- Infinity / Infinity => NaN
- 求模
- Infinity % _ => NaN
- _ % 0 => NaN
加性操作符
包括加法和减法,其中加法需要注意字符串拼接。
关系操作符
字符串的比较并不是按照字母表顺序,而是比较两个字符串中对应位置的每个字符的字符编码值。在编码表中,大写字母的字符编码全部小于小写字母的字符编码,因此如果要按照字母表顺序比较,必须转换为均小/大写。NaN和任何内容进行比较均为false。
1 | "alphabet" < "Brick" // false |
相等操作符
常规的字符串、数值、布尔值的相等性很好判断,但是涉及到对象就比较复杂了。最早的ECMAScript会在执行相等操作符之前,将对象转换为相似的类型,但这种转换的合理性存在一定的争议。因此ECMAScript提出了两组操作符:相等和不相等——先转换再比较,全等和不全等——不转换直接比较。
相等和不相等
在判断相等之前,操作数会被进行强制转型,针对不同数据类型的转换规则如下:
- Boolean ? _ => 将布尔值转化为数值
- String ? Number => 将字符串转换为数值
- Object ? non-object-type => 调用对象的valueOf()方法,将得到的基本类型值
比较时所遵循的规则如下:
- null和undefined相等
- 在比较相等性之前,不能将null和undefined转换成其他任何值
- NaN和任何值都不相等,包括NaN本身
- 两个对象判断相等的依据是比较它们是不是指向同一个对象
全等和不全等
全等操作符为三个等于号(===),它判断的依据是比较两个操作数在未经转换的情况下是否相等。
1 | null === undefined // false,因为它们是不同类型的值 |
条件操作符
即var num = boolean_expr ? true_val : false_val;
。
赋值操作符
逗号操作符
逗号操作符可用于声明多个变量。
1 | var num1 = 1, num2 = 2, num3 = 3; |
逗号操作符还可以用于赋值。在用于赋值时,逗号操作符总会返回表达式中的最后一项。
1 | var num = (5, 1, 4, 8, 0); // num的值为0 |
语句
常规用法在此不赘述,记录几个不太熟的语句。
for语句
其中有一个特殊点在于,ECMAScript中不存在块级作用域,因此循坏内部定义的变量可以在外部被访问到。
1 | var count = 10; |
for-in语句
for-in语句是一种精准的迭代语句,可以用来枚举对象的属性。ECMAScript对象的属性没有顺序,因此如果使用for-in循环输出的内容顺序是不可预测的。在ECMAScript3中,如果要迭代的对象的变量值为null或undefined会产生报错,ECMAScript5中将其修正为不再报错,但是会不执行循环体。
for-in语句有点奇怪之处,举例如下:
1 | var data = [{ |
原因在于for-in语句的设计初衷是用于遍历对象中的属性的,用它来遍历数组很容易错用。详情参考:
Why is using for-in with array iteration a bad idea?
Loop through an array in JavaScript
label语句
在代码中添加标签,可被break或continue语句引用。
1 | // 其中的outermost即为标签 |
1 | var num = 0; |
with语句
with语句的作用是将代码的作用域设置到一个特定的对象中,以简化多次编写同一个对象的工作。但是大量使用with语句会导致性能下降和调试困难,因此不建议使用。严格模式下使用with语句会被视为语法错误。
1 | // 化简前 |
在with语句的代码块内部,每个变量首先被认为是一个局部变量,如果在局部环境中找不到该变量的定义,就会查询location对象中是否有同名的属性,若有则将该属性的值作为变量的值。
switch语句
与其他语言的switch语句的不同之处在于:
- 可以在switch语句中使用任何数据类型,而不是只能使用数值
- 每个case的值不一定是常量,可以是变量、表达式注意:switch语句在比较值时使用全等操作符,因此不会发生类型转换,如”10”不等于10。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19switch("hello world") {
case "hello" + "world":
alert("Greeting sent");
break;
case "bye":
alert("Farewell sent");
break;
default:
alert("None sent");
}
var num = 25;
switch(true) {
case num < 0:
alert("negative");
break;
case num >= 0 && num <= 10:
...
}
函数
ECMAScript函数在定义时不必指定是否返回值,未指定返回值的函数返回的是undefined。
理解参数
ECMAScript函数不介意传递进来多少个参数,也不在乎传进来的参数是什么类型。原因在于ECMAScript中的参数在内部是用一个数组来表示的,函数接收到的是这个数组,而不关心其中的内容。在函数体内可以通过arguments对象来访问这个参数数组(但arguments只是与数组类似,而非Array的实例),如arguments[0]、arguments[1]等,也可以通过其length属性获取参数个数。
函数可以在不包含命名参数的情况下,使用arguments访问真实传入的参数。
1 | function sayHi() { |
arguments对象可以与命名参数一起使用,它的值也永远与对应命名参数的值保持同步。虽然是保持同步的,但是读取这两个值时并不会访问同一块内存空间,它们的内存空间是独立的。没有传递值的命名参数将自动被赋予为undefined。
1 | function doAdd(num1, num2) { |
严格模式下对arguments对象做出了一定限制:对没有传递值的命名参数的赋值无效,仍然为undefined;重写arguments的值会导致语法错误。
ECMAScript中的所有参数传递的都是值,不可能通过引用传递参数。
没有重载
一般语言的重载是为一个函数编写两个定义,只要两个定义的签名(接收参数的类型和数量)不同即可。而ECMAScript函数没有签名,所以不能做到真正的重载,但是可以通过对arguments.length不同值设置不同反应来模拟重载。如果在ECMAScript中定义两个名字相同的函数,则该名字只属于后定义的函数。