变量和值
JavaScript 整数最大能达到53位。
| 0 可以将变量a 中的数值转换为32 位有符号整数,因为数位运算符| 只适用于32 位整数(它只关心32 位以内的值,其他的数位将被忽略)
JavaScript 引用指向的是值。如果一个值有10 个引用,这些引用指向的都是同一个值,它们相互之间没有引用/ 指向关系。JavaScript 对值和引用的赋值/ 传递在语法上没有区别,完全根据值的类型来决定。
undefined 类型只有一个值,即undefined。null 类型也只有一个值,即null。它们的名称既是类型也是值。
null 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而undefined 却是一个标识符,可以被当作变量来使用和赋值。
表达式void ___ 没有返回值,void 并不改变表达式的结果,只是让表达式不返回值。
NaN 意指“不是一个数字”(not a number),将它理解为“无效数值”“失败数值”或者“坏数值”可能更准确些。
简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值/ 传递,包括null、undefined、字符串、数字、布尔和ES6 中的symbol。
复合值(compound value)——对象(包括数组和封装对象,参见第3 章)和函数,则总是通过引用复制的方式来赋值/ 传递。
请记住:我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。
原生函数
- Array()
- Array 构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而非只充当数组中的一个元素。
类型转换
1.显式类型转换
- 基本类型值的字符串化规则为:null 转换为"null",undefined 转换为"undefined",true转换为"true"。数字的字符串化则遵循通用规则,不过那些极小和极大的数字使用指数形式
- 所有安全的JSON 值(JSON-safe)都可以使用JSON.stringify(..) 字符串化。安全的JSON 值是指能够呈现为有效JSON 格式的值。(undefined、function、symbol(ES6+)和包含循环引用(对象之间相互引用,形成一个无限循环)的对象都不符合JSON结构标准,支持JSON 的语言无法处理它们。)
- JSON.stringify(..) 在对象中遇到undefined、function 和symbol 时会自动将其忽略,在数组中则会返回null(以保证单元位置不变)。
- 如果对象中定义了toJSON() 方法,JSON 字符串化时会首先调用该方法,然后用它的返回值来进行序列化。可以用toJSON()方法对含有非法JSON值的对象返回一个安全的JSON值。
- +c 是+ 运算符的一元(unary)形式(即只有一个操作数)。+ 运算符显式地将c 转换为数字,而非数字加法运算。 一元运算符+ 的另一个常见用途是将日期(Date)对象强制类型转换为数字
- ~操作。它首先将值强制类型转换为32 位数字,然后执行字位操作“非”(对每一个字位进行反转)。
- 对~ 还可以有另外一种诠释,源自早期的计算机科学和离散数学:~ 返回2 的补码
- ~x 大致等同于-(x+1)
- ~ 和indexOf() 一起可以将结果强制类型转换(实际上仅仅是转换)为真/ 假值:
- JavaScript 有一处奇特的语法,即构造函数没有参数时可以不用带()。于是我们可能会碰到var timestamp = +new Date; 这样的写法。
- “抽象渗漏”,意思是在代码中暴露了底层的实现细节,比如在使用indexOf()方法时,判断条件使用了===-1和>==0。
- 解析字符串中的数字(parseInt( a );)和将字符串强制类型转换为数字(Number( a );)的返回结果都是数字。但解析和转换两者之间还是有明显的差别。
- 解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回NaN。
- 不要忘了parseInt(..) 针对的是字符串值。向parseInt(..) 传递数字和其他类型的参数是没有用的,比如true、function(){...} 和[1,2,3]。
- ES5 之前的parseInt(..) 有一个坑导致了很多bug。即如果没有第二个参数来指定转换的基数(又称为radix),parseInt(..) 会根据字符串的第一个字符来自行决定基数。
-
parseInt( 1/0, 19 ); // 18
。parseInt(..) 先将参数强制类型转换为字符串再进行解析,parseInt(1/0, 19) 实际上是parseInt("Infinity", 19)。第一个字符是"I"('I'在19~36进制下可以被转换为数字,为18),以19 为基数时值为18。第二个字符"n" 不是一个有效的数字字符,解析到此为止
2.隐式类型转换
- 隐式强制类型转换的作用是减少冗余,让代码更简洁。
- 如果+ 的其中一个操作数是字符串(或者通过以上步骤可以得到字符串),则执行字符串拼接;否则执行数字加法。
-
a + ""
(隐式)和前面的String(a)(显式)之间有一个细微的差别需要注意。根据ToPrimitive 抽象操作规则,a + "" 会对a 调用valueOf() 方法,然后通过ToString 抽象操作将返回值转换为字符串。而String(a) 则是直接调用ToString()。 - -是数字减法运算符,因此
a - 0
会将a 强制类型转换为数字。也可以使用a * 1
和a/1
,因为这两个运算符也只适用于数字,只不过这样的用法不太常见。 -
&&
和||
运算符的返回值并不一定是布尔类型,而是两个操作数其中一个的值。- 对于|| 来说,如果条件判断结果为true 就返回第一个操作数(a 和c)的值,如果为false 就返回第二个操作数(b)的值。
- && 则相反,如果条件判断结果为true 就返回第二个操作数(b)的值,如果为false 就返回第一个操作数(a 和c)的值。
3.宽松相等和严格相等
- 宽松相等(loose equals)== 和严格相等(strict equals)=== 都用来判断两个值是否“相等”,但是它们之间有一个很重要的区别,特别是在判断条件上。:“== 允许在相等比较中进行强制类型转换,而=== 不允许。”
- 抽象相等
x==y
- 字符串和数字之间的相等比较
- 如果Type(x) 是数字,Type(y) 是字符串,则返回x == ToNumber(y) 的结果。
- 如果Type(x) 是字符串,Type(y) 是数字,则返回ToNumber(x) == y 的结果。
- 其他类型和布尔类型之间的相等比较
- 如果Type(x) 是布尔类型,则返回ToNumber(x) == y 的结果;
- 如果Type(y) 是布尔类型,则返回x == ToNumber(y) 的结果。
个人建议无论什么情况下都不要使用== true 和== false。
- null 和undefined 之间的相等比较
- 如果x 为null,y 为undefined,则结果为true。
- 如果x 为undefined,y 为null,则结果为true。
这也就是说在== 中null 和undefined 是一回事,可以相互进行隐式强制类型转换
var b; a == b; // true a == null; // true b == null; // true
- 对象和非对象之间的相等比较
- 如果Type(x) 是字符串或数字,Type(y) 是对象,则返回x==ToPrimitive(y) 的结果;
- 如果Type(x) 是对象,Type(y) 是字符串或数字,则返回ToPromitive(x) == y 的结果。
但有一些值不这样,原因是== 算法中其他优先级更高的规则。
因为没有对应的封装对象,所以null 和undefined 不能够被封装(boxed),Object(null)和Object() 均返回一个常规对象。var a = null; var b = Object( a ); // 和Object()一样 a == b; // false var c = undefined; var d = Object( c ); // 和Object()一样 c == d; // false var e = NaN; var f = Object( e ); // 和new Number( e )一样 e == f; // false
- ""、"\n"(或者" " 等其他空格组合)等空字符串被ToNumber 强制类型转换为0。
- 假值的相等比较
"0" == null; // false "0" == undefined; // false "0" == false; // true -- 晕! "0" == NaN; // false "0" == 0; // true "0" == ""; // false false == null; // false false == undefined; // false false == NaN; // false false == 0; // true -- 晕! false == ""; // true -- 晕! false == []; // true -- 晕! false == {}; // false "" == null; // false "" == undefined; // false "" == NaN; // false "" == 0; // true -- 晕! "" == []; // true -- 晕! "" == {}; // false 0 == null; // false 0 == undefined; // false 0 == NaN; // false 0 == []; // true -- 晕! 0 == {}; // false
- 字符串和数字之间的相等比较
语句
1 语句的结果值
- 语句都有一个结果值(statement completion value,undefined 也算)。
- 语法不允许我们获得语句的结果值并将其赋值给另一个变量(至少目前不行)。
2 表达式的副作用
- 最常见的有副作用(也可能没有)的表达式是函数调用:
function foo() {
a = a + 1;
}
var a = 1;
foo(); // 结果值:undefined。副作用:a的值被改变
- ++ 在前面时,如
++a
,它的副作用(将a 递增)产生在表达式返回结果值之前,而a++
的副作用则产生在之后。
*++a++
会产生ReferenceError 错误,因为运算符需要将产生的副作用赋值给一个变量。以++a++
为例,它首先执行a++
(根据运算符优先级,如下),返回42,然后执行++42
,这时会产生ReferenceError 错误,因为++ 无法直接在42 这样的值上产生副作用。 - 链式赋值常常被误用,例如
var a = b = 42
,看似和前面的例子差不多,实则不然。如果变量b 没有在作用域中象var b
这样声明过,则var a = b =42
不会对变量b 进行声明。在严格模式中这样会产生错误,或者会无意中创建一个全局变量
3 上下文规则
{
foo: bar()
}
foo: bar()
叫作“标签语句”,foo
是语句bar()
的标签。
- JavaScript 通过标签跳转能够实现goto 的部分功能。continue 和break 语句都可以带一个标签,因此能够像goto 那样进行跳转。例如:
foo: for (var i=0; i<4; i++) {
for (var j=0; j<4; j++) {
// 如果j和i相等,继续外层循环
if (j == i) {
// 跳转到foo的下一个循环
continue foo;
}
// 跳过奇数结果
if ((j * i) % 2 == 1) {
// 继续内层循环(没有标签的)
continue;
}
console.log( i, j );
}
}
contine foo 并不是指“跳转到标签foo 所在位置继续执行”,而是“执行
foo 循环的下一轮循环”。所以这里的foo 并非goto。
- 带标签的循环跳转一个更大的用处在于,和break __ 一起使用可以实现从内层循环跳转到外层循环。
foo: for (var i=0; i<4; i++) {
for (var j=0; j<4; j++) {
if ((i * j) >= 3) {
console.log( "stopping!", i, j );
break foo;
}
console.log( i, j );
}
}
break foo 不是指“跳转到标签foo 所在位置继续执行”,而是“跳出标签
foo 所在的循环/ 代码块,继续执行后面的代码”。
- JSON 的确是JavaScript 语法的一个子集,但是JSON 本身并不是合法的JavaScript 语法。(JSON 属性名必须使用双引号!)