《YDKJS 中卷》读书笔记

《YDKJS 中卷》读书笔记

类型

7 个内置类型:stringnumberbooleanobjectundefinedsymbolnull

1
2
3
4
5
6
7
8
9
10
11
12
typeof undefined      === "undefined";
typeof true === "boolean";
typeof 42 === "number";
typeof "42" === "string";
typeof {} === "object";
typeof null === "object";
typeof [] === "object";
typeof Symbol() === "symbol";
typeof function () {} === "function"

// 检测a是否为null
(!a && typeof a === "object")) === true;

函数对象

函数对象是object的一个子类型,具有一个内部属性[[call]],该属性使其可以被调用。

属性 含义
[[call]] 表明该对象是一个可调用对象
length 函数对象声明的行参个数

类数组

可以使用Array.from()将类数组(例如 arguments)转换为真正的数组。

数字

JS 没有真正的整数,它的number类型实际是由IEEE 754[1]标准的双精度浮点数表示的。

浮点数相等问题:

1
2
3
4
5
6
0.1 + 0.2 === 0.3; // false

// 使用机器精度作为误差范围比较两个数字是否相等
function numberCloseEnoughToEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON;
}

NaN是一个特殊值,它和自身不相等,检查是否是NaN可以使用Number.isNaN()

异步

JS 在 ES6 中引入的Promise标志着在技术上正式将异步纳入到 JS 引擎的势力范围,引擎能够对事件循环队列的调度运行做到精细的控制。

任务队列

ES6 在事件循环队列(Event Loop Queue)之上建立了一个新的概念:任务队列(Job Queue),任务队列是挂在事件循环队列每个 tick 之后的一个队列。在事件循环的每个 tick 中出现的异步动作不会添加到事件队列,而是直接添加到该 tick 后的任务队列。

Promise的异步特性是基于任务的!!!

异步回调的问题

  1. 可维护性:人的思考方式是顺序的,太多的异步回调导致程序的执行流程无法很容易的被人梳理出来,代码难以维护和更新
  2. 信任问题:回调的调用方是不受控制的(可能是第三方代码),不能保证回调函数一定会按预期被调用,这也是回调最大的问题

Promise

Promise把未来事件封装组合起来,我们在Promise中侦听未来事件的状态并且根据状态决定下一步该做什么(resolve, reject),代码的执行控制权又回到了我们手里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const p = new Promise((resolve, reject) => {
console.log("Promise constructor executed");

setTimeout(function () {
resolve(100);
}, 3000);
reject(new Error("reject error message"));
throw new Error("throw error message");
})
.then(
(val) => console.log("resolved value:", val),
(err) => console.log("first handle error:", err)
)
.then((val) => console.log("second resolved value", val))
.catch((err) => console.log("second handle error:", err));

// 在其他地方使用instanceof判断是否是Promise可能不太准确,需要使用thenable接口去判断
console.log("p is Promise: ", p instanceof Promise);
// => Promise constructor executed
// => p is Promise: true
// => first handle error: Error: reject error message <Error Stack>
// => second resolved value: undefined
  1. Promise 传入的函数会立即被调用
  2. Promise 一旦被决议(resolve,reject)就不会再改变,上面代码没有输出resolved valuethrow error message
  3. resolve()会将传入的 Promise 展开而 reject()不会改变传入的值
  4. catch()和 then()函数
    1. 返回新的 Promise
    2. 被异步调用
    3. 形式参数是回调函数,回调函数的实参是 Promise 决议的值,then()回调函数的返回值(Promise 会被展开)被当作新 Promise 的“完成”决议值,相当于return Promise.resolve(xxx)
    4. 函数中如果抛出异常会使下一个 Promise 立即被拒绝

Promise.all()

接受一个 Promise 集合,返回一个新的 Promise。如果 Promise 集合中的所有 Promise 都完成,那么返回的 Promise 完成;只要集合中有一个 Promise 失败,则返回的 Promise 失败。

Promise.allSettled()

接受一个 Promise 集合,返回一个新的 Promise。新的 Promise 的决议值为每个 Promise 的决议结果和值

1
2
3
4
5
6
7
8
9
10
11
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) =>
setTimeout(reject, 100, "foo")
);
const promises = [promise1, promise2];

Promise.allSettled(promises).then((results) =>
results.forEach((item) => console.log(item))
);
// => Object { status: "fulfilled", value: 3 }
// => Object { status: "rejected", reason: "foo" }

Promise.race()

下面利用Promise.race()创建一个带超时功能的 Promise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function someUnstableTask() {
return new Promise((resolve) =>
setTimeout(() => {
resolve("task finished");
console.log("task running...");
}, 5000)
);
}

function timeout(interval) {
return new Promise((resolve, reject) => {
setTimeout(() => reject("task timeout"));
}, interval);
}

Promise.race([someUnstableTask(), timeout(1000)])
.then((val) => console.log(val))
.catch((err) => console.error(err));

// => task timeout
// => task running...

从上面代码可以看出:

  1. Promise.race()接受一组 Promise 并返回一个具有竞态条件新的 Promise:组内任意一个被决议的 Promise 都会让新的 Promise 被决议
  2. Promise.race()即使被决议也不能停止正在执行的 Promise,它们会继续执行

Promise.resovle()和 Promise.reject()

这两个函数会将传入的值 Promise 化,它们是可信任的 Promise 封装工具。

Promise.prototype.finally()

  1. 方法返回一个 Promise
  2. 在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数
  3. 回调函数没有行参,回调函数中抛出的异常将会使下一个链中下一个 Promise 立即决议失败

小结

  1. Promise 解决了因只用回调的代码而备受困扰的控制反转问题
  2. Promise 仍然依赖回调,只是把回调交给了一个位于我们和其他工具之间的可信任的中介机制
  3. 初步提供了一个以顺序方式表达异步流的方法,尽管还不是很完善

Promise 主要解决因回调造成的控制反转而带来的信任问题!!!

生成器(generator)

JS 并不是多线程支持抢占式的语言,一个函数一旦执行就只能运行到结束,但是 ES6 引入了一个新的函数类型,可以以合作式的方式实现函数的暂停,这个函数就是生成器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let x = 0;

// 注意函数名称前的 *
// 有三种形式(推荐第二种):
// 1. function* foo() {}
// 2. function *foo() {}
// 3. function*foo() {}
function* foo() {
x = 1;
x = yield 10; // 暂停!
}

// 1
const it = foo();
console.log("it:", Object.prototype.toString.call(it), "x:", x); // => it: [object Generator] x: 0

// 2
let ret = it.next(2);
console.log("ret:", ret, "x:", x); // => ret: { value: 10, done: false } x: 1

// 3
ret = it.next(100);
console.log("ret:", ret, "x:", x); // => ret: { value: undefined, done: true } x: 100

ret = it.next(1000);
console.log("ret:", ret, "x:", x); // => ret: { value: undefined, done: true } x: 100
  1. 调用生成器函数并没有真正执行函数体,而是生成并返回来一个迭代器(iterator),这个迭代器在后面会控制生成器的执行
  2. next()会从当前位置(继续)执行函数体,遇到yield则暂停执行,yield后面的表达式会立即求值并作为next()迭代的值
  3. 传入next()的值作为被暂停的yield 表达式的结果
  4. yield消息传递是双向的,“先出后入”,所以next()调用的次数要比yield多一次
  5. 生成器对象既是迭代器(next()),也是可迭代对象(@@iterator)

迭代器

下面是 JS 表示的一种迭代器设计模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const something = (function () {
let nextValue;

return {
// for ... of循环需要,调用该方法返回一个新的迭代器对象
[Symbol.iterator]: function () {
return this;
},
// 标准迭代器接口方法
next: function () {
if (nextValue === undefined) {
nextValue = 1;
} else {
nextValue = 3 * nextValue * 5;
}

// 返回当前迭代的值
return { done: false, value: nextValue };
},
};
})();

console.log(something.next().value); // => 1
console.log(something.next().value); // => 15
console.log(something.next().value); // => 225
console.log(something.next().value); // => 3375

// something也具有iterator属性,可以被for...of迭代
for (const v of something) {
console.log(v);

// 防止死循环
if (v > 4000000) {
break;
}
}
// => 50625
// => 759375
// => 11390625

Generator.prototype.return()

return() 方法返回给定的值并结束生成器。

1
2
3
4
5
6
7
8
9
10
function* gen() {
yield 1;
yield 2;
yield 3;
}

var g = gen();

console.log(g.next()); // => { value: 1, done: false }
console.log(g.return("foo")); // => { value: "foo", done: true }

如果对已经处于“完成”状态的生成器调用 return(value),则生成器将保持在“完成”状态。如果提供了参数,则参数将被设置为返回对象的 value 属性的值。

Generator.prototype.throw()

throw() 方法用来向生成器抛出异常,异常可以在生成器内被捕获。

1
2
3
4
5
6
7
8
9
10
function* gen() {
while (true) {
yield 42;
}
}

var g = gen();
console.log(g.next()); // => { value: 42, done: false }
console.log(g.throw(new Error("Something went wrong")));
// => Error: Something went wrong <Stack>

Async Generator Runner

下面利用前面所学到的知识,编写一个支持异步的 generator runner:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function* generator() {
let data1, data2, data3, data4;
data1 = yield fetchSomething("a");
try {
data2 = yield fetchSomething("b", true);
} catch (e) {
console.log("catch error: ", e);
}
data3 = yield fetchSomething("c");
data4 = yield fetchSomething(`${data1},${data2},${data3}`);
return data4;
}

function fetchSomething(url, willError) {
return new Promise((resolve, reject) => {
if (willError) {
return reject(new Error("timeout"));
}
setTimeout(() => resolve(url), Math.floor(Math.random() * 1e3));
});
}

function generatorRunner(...args) {
if (args.length === 0) {
throw new Error("generator can not be null");
}
const generator = args.shift();
if (typeof generator !== "function") {
throw new Error("generator must be a generator function");
}
const it = generator.apply(this, args);

const handleNext = function (value) {
let ret = it.next(value);
return (function handleResult(ret) {
if (ret.done) {
return value;
}

return Promise.resolve(ret.value)
.then(handleNext)
.catch((e) => handleResult(it.throw(e)));
})(ret);
};

return Promise.resolve().then(handleNext);
}

generatorRunner(generator).then((a) =>
console.log("fetchSomething return: ", a)
);

// => catch error: Error: timeout <Stack>
// => fetchSomething return: a,undefined,c

  1. IEEE 二進位浮點數算術標準(IEEE 754) ↩︎

作者

m3m0ry

发布于

2020-08-08

更新于

2020-09-20

许可协议

评论