第一部分 类型与语法
第2章 值
2.4 特殊数值
2.4.1 不是值的值
undefined和null类型只有一个值,名称既是类型也是值undefined=== 从未赋值、没有值null=== 曾赋过值,但目前没有值,空值
2.4.3 特殊的数字
1. 不是数字的数字
- NaN:指无效的数字,唯一一个非自反的值,
NaN !== NaN为true - 内建的全局工具函数 isNaN(..) 有一个严重的缺陷,它检查参数是否不是 NaN,也不是数字
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true——晕!
从 ES6 开始我们可以使用工具函数 Number.isNaN(..)。
ES6 之前的浏览器的 polyfill 如下:
if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" &&
window.isNaN( n )
); };
}
Number.isNaN( a ); // true
Number.isNaN( b ); // false——好!
2. 无穷数
Infinity === Number.POSITIVE_INfiNITY
3. 零值
- -0 除了可以用作常量以外,也可以是某些数学运算的返回值
var a = 0 / -3; // -0
var b = 0 * -3; // -0
- 加法和减法运算不会得到负零
- 将
-0字符串化会返回'0',将字符串'-0'数字化则得到-0 JSON.stringify(-0) === JSON.stringify(-0)
JSON.stringify(-0) === JSON.stringify(-0) // '0' === '0'
JSON.parse('-0') === JSON.parse('0')
JSON.parse('-0') === JSON.parse('0') // -0 === 0
第4章 强制类型转换
4.5 宽松相等和严格相等
4.5.2 抽象相等
非常规情况
- NaN不等于NaN
- +0不等于-0
1.字符串与数字之间的相待比较
(1) 如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。
(2) 如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果。
const a = 42
const b = '42'
a === b // false
a == b // true
2.其他类型和布尔类型之间的相等比较
(1) 如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果;
(2) 如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果。
const x = 'true'
const y = '42'
x == y // false
const x = '42'
const y = 'false'
x == y // false
3.null和undefined之间的相等比较
(1) 如果 x 为 null,y 为 undefined,则结果为 true。
(2) 如果 x 为 undefined,y 为 null,则结果为 true。
const a = null
let b
a == b // true
b == null // true
4. 对象和非对象之间的相等比较
(1) 如果 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;
(2) 如果 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果。
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive检查该值是否有 valueOf() 方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString()的返回值(如果存在)来进行强制类型转换。 如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
const a = 42;
const b = [ 42 ];
a == b; // true
4.5.3 比较少见的情况
1. 返回其他数字
Number.prototype.valueOf = function() {
return 3;
};
new Number( 2 ) == 3; // true
2. 假值的相等比较
非常规
"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
false == {}; // true
"" == 0; // true
"" == []; // true
"" == {}; // true
0 == []; // true
3. 极端情况
[] == ![]; // true
2 == [2]; // true
"" == [null]; // true
4.6 抽象关系比较
“抽象关系比较”(abstract relational comparison),分为两个部 分:比较双方都是字符串(后半部分)和其他情况(前半部分)。
- 比较双方首先调用 ToPrimitive,如果结果出现非字符串,就根据 ToNumber 规则将双方强 制类型转换为数字来进行比较。
var a = [ 42 ];
var b = [ "43" ];
a < b; // true
b < a; // false
- 如果比较双方都是字符串,则按字母顺序来进行比较:
var a = [ "42" ];
var b = [ "043" ];
a < b; // false
第5章 语法
5.1 语句和表达式
5.1.3 上下文规则
1.大括号
(1) 对象常量
(2) 标签
{
foo: bar()
}
// 标签为foo的循环
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 );
}
}
// 1 0
// 2 0
// 2 1
// 3 0
// 3 2
2.代码块
[] + {}; // "[object Object]"
{} + []; // 0
第一行代码中,{} 出现在 + 运算符表达式中,因此它被当作一个值(空对象)来处理。第 4 章讲过 [] 会被强制类型转换为 "",而 {} 会被强制类型转换为 "[object Object]"。
但在第二行代码中,{} 被当作一个独立的空代码块(不执行任何操作)。代码块结尾不需 要分号,所以这里不存在语法上的问题。最后 + [] 将 [] 显式强制类型转换(参见第 4 章) 为 0。
3. 对象解构
const obj = { a: 42, b: "foo" };
const { a, b } = obj;
console.log(a, b); // 42, "foo"
4. else if 和可选代码块
if (a) {
// ..
}
else if (b) {
// ..
}
else {
// ..
}
事实上 JavaScript 没有 else if,但 if 和 else 只包含单条语句的时候可以省略代码块的 { }。
所以上面的 else if 其实是这样的:
if (a) {
// ..
}
else {
if (b) {
// ..
}
else {
// ..
}
}
第二部分 异步和性能
第3章 Promise
3.4 链式流
术语:决议、完成以及拒绝
术语决议(resolve)、完成(fulfill)和拒绝(reject),构造器 Promise(..):
var p = new Promise( function(X,Y){
// X()用于完成
// Y()用于拒绝
} );
3.8 Promise局限性
3.8.1 顺序错误处理
如果构建了一个没有错误处理函数的 Promise 链,链中任何地方的任何错误都会在链中一 直传播下去,直到被查看(通过在某个步骤注册拒绝处理函数)。
var p = foo( 42 )
.then( STEP2 )
.then( STEP3 );
可以在 p 上注册一个拒绝错误处理函数,对于链中任何位置出现的任何错误,这个处理函数都会 得到通知:
p.catch( handleErrors );
3.8.2 单一值
Promise 只能有一个完成值或一个拒绝理由。在简单的例子中,这不是什么问 题,但是在更复杂的场景中,你可能就会发现这是一种局限了。
3.8.3 单决议
Promise 最本质的一个特征是:Promise 只能被决议一次(完成或拒绝)。
3.8.4 惯性
Promise 提供了一种不同的范式,因此,编码方式的改变程度从某处的个别差异到某种情 况下的截然不同都有可能。
3.8.5 无法取消的 Promise
一旦创建了一个 Promise 并为其注册了完成和 / 或拒绝处理函数,如果出现某种情况使得 这个任务悬而未决的话,你也没有办法从外部停止它的进程。
3.8.6 Promise 性能 这个特定的局限性既简单又复杂。
把基本的基于回调的异步任务链与 Promise 链中需要移动的部分数量进行比较。很显然, Promise 进行的动作要多一些,这自然意味着它也会稍慢一些。
第4章 生成器
4.1 打破完整运行
多数 JavaScript 文档和代码中的生成器声明格式都是 function* foo() { .. },而不是我这里使用的 function foo() { .. }: 唯一区别是 * 位置的风格不同。这两种形式在功能和语法上都是等同的,还 有一种是functionfoo(){ .. }(没有空格)也一样。
4.2 生成器产生值
4.2.1 生产者与迭代器
实现一个直接使用函数闭包的版本(参见本系列的《你不知道的 JavaScript(上卷)》 的“作用域和闭包”部分),类似如下:
var gimmeSomething = (function(){
var nextVal;
return function(){
if (nextVal === undefined) {
nextVal = 1;
}
else {
nextVal = (3 * nextVal) +6;
}
return nextVal;
};
})();
gimmeSomething();
gimmeSomething();
gimmeSomething();
gimmeSomething();
// 1
// 9
// 33
// 105
迭代器是一个定义良好的接口,用于从一个生产者一步步得到一系列值。JavaScript 迭代器的接口,与多数语言类似,就是每次想要从生产者得到下一个值的时候调用 next()。 可以为我们的数字序列生成器实现标准的迭代器接口:
var something = (function(){
var nextVal;
return {
// for..of循环需要
[Symbol.iterator]: function(){ return this; },
// 标准迭代器接口方法
next: function(){
if (nextVal === undefined) {
nextVal = 1;
}
else {
nextVal = (3 * nextVal) + 6;
}
return { done:false, value:nextVal };
}
};
})();
something.next().value;
something.next().value;
something.next().value;
something.next().value;
// 1
// 9
// 33
// 105
ES6 还新增了一个 for..of 循环,这意味着可以通过原生循环语法自动迭代标准迭代器:
for (var v of something) {
console.log( v );
// 不要死循环!
if (v > 500) {
break;
}
}
// 1 9 33 105 321 969
手工在迭代器上循环,调用 next() 并检查 done:true 条件来确定何时停止循环:
for (
var ret;
(ret = something.next()) && !ret.done;
){
console.log( ret.value );
// 不要死循环!
if (ret.value > 500) {
break;
}
}
// 1 9 33 105 321 969
4.3 异步迭代生成器
function *main(){
yield setTimeout(() => console.log(1000), 3000)
console.log('在1000之后')
yield 'vincent'
}
var it = main()
it.next() // {value: 829, done: false}
// 3秒后打印出1000
it.next()
//在1000之后
// {value: "vincent", done: false}
4.4 生成器 +Promise
function *main(){
var dd = yield Promise.resolve(100)
console.log('dd', dd)
console.log('在100之后')
yield 'vincent'
}
it = main() // main {<suspended>}
p = it.next().value // Promise {<resolved>: 100}
p.then(d => console.log(it.next(d)))
// dd 100
// 在100之后
// {value: "vincent", done: false}