# ES6 难点
# Iterator 和 for...of 循环
# Iterator 遍历器概念
ES6有四种数据集合,Array,对象,Map,Set。
Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
Iterator 的作用有三个:
- 一是为各种数据结构,提供一个统一的、简便的访问接口;
- 二是使得数据结构的成员能够按某种次序排列;
- 三是 ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费。
Iterator 的遍历过程
- 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值(true结束,false未结束),表示遍历是否结束。 对于遍历器对象来说,done: false和value: undefined属性都是可以省略的
代码示例
function makeIterator(arr) {
var nextIndex = 0
return {
next() {
return nextIndex < arr.length
? {value: arr[nextIndex++], done: false}
: { value:undefined, done: true }
}
}
}
function makeIterator(arr) {
var nextIndex = 0
return {
next() {
return nextIndex < arr.length
? {value: arr[nextIndex++]}
: {done: true }
}
}
}
var it = makeIterator([1,3])
console.log(it.next());
console.log(it.next());
console.log(it.next());
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
# 默认 Iterator 接口
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of
循环.当使用for...of
循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
一种数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。 执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内
var obj = {
[Symbol.iterator]() {
return {
next() {
return {
value:1,
done:true
}
}
}
}
}
console.log(obj[Symbol.iterator]().next());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
上面代码中,对象obj是可遍历的(iterable),因为具有Symbol.iterator属性
ES6 的有些数据结构原生具备 Iterator 接口,凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
下面的例子是数组的Symbol.iterator属性。
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
2
3
4
5
6
7
# 调用Iterator接口的场合
- for ... of
- 对数组和 Set 结构进行解构赋值时
let [first, ...rest] = new Set().add('a').add('b').add('c');
- 扩展运算符
let arr = ['b', 'c'];let arr2 = ['a', ...arr, 'd']
- yield* yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
2
3
4
5
6
7
8
9
10
11
12
13
14
- 其他场合
- for...of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
- Promise.all()
- Promise.race()
# Iterator 接口与 Generator 函数
Symbol.iterator()方法的最简单实现,还是使用下一章要介绍的 Generator 函数。
var obj = {
*[Symbol.iterator]() {
yield {name:1}
yield {age:100}
}
}
for(const key of obj) {
console.log('key :>> ', key);
if key.name
}
2
3
4
5
6
7
8
9
10
# 遍历器对象的 return(),throw() (opens new window)
遍历器对象除了具有next()方法,还可以具有return()方法和throw()方法。如果你自己写遍历器对象生成函数,那么next()方法是必须部署的,return()方法和throw()方法是否部署是可选的。
return()方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句),就会调用return()方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return()方法。
var obj = {
*[Symbol.iterator]() {
yield {name:1}
yield {age:100}
}
}
for(const key of obj) {
if( key.name){
console.log('key :>> ', key);
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
# for...of 循环
一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of
【循环内部调用的是数据结构的Symbol.iterator方法】。
for...of的本质是一个while循环,所以上面的代码实质上执行的是下面的逻辑。
# Set 和 Map 结构
Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for...of
循环。
const engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"])
for(const eng of engines){
console.log('eng :>> ', eng);
}
const mapEngines = new Map([['edition', 6],["committee", "TC39"],["standard", "ECMA-262"]])
for(const [key,value] of mapEngines){
console.log(key,value)
}
2
3
4
5
6
7
8
9
10
上面代码演示了如何遍历 Set 结构和 Map 结构。值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
# 对象遍历
使对象可以用for ... of遍历
var obj = {
name:1,
age: 2
}
// 方式一
obj[Symbol.iterator] = function* () {
for(const key in this) {
yield [key, this[key]]
}
}
for (let [key, value] of obj) {
console.log(key, '->', value);
}
// 方式二
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
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
# Generator 函数的语法
Generator 函数是 ES6 提供的一种异步编程解决方案。
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象. 必须调用遍历器对象的next方法,使得指针移向下一个状态
Generator 函数是一个普通函数,有两个特征。
- 一是,function关键字与函数名之间有一个星号;
- 二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)
function* helloWorldGenerator() {
console.log(1)
yield 'hello';
console.log(2)
yield 'world';
console.log(3)
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
# yield 表达式
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
遍历器对象的next方法的运行逻辑如下
- 遇到yield表达式,就暂停执行后面的操作,并将紧跟在【yield后面的那个表达式的值,作为返回的对象的value属性值】。
- 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
- 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值
- 如果该函数没有return语句,则返回的对象的value属性值为undefined。
需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能
function* gen() {
yield 123 + 456;
}
var it = gen() // 此时yield 后面的 123 + 456还没执行
it.next() // yield 后面的 123 + 456才执行
2
3
4
5
6
7
# 与 Iterator 接口的关系
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口
var obj = {}
obj[Symbol.iterator] = function*() {
yield 1;
yield 2;
yield 3;
}
for(const key of obj) {
console.log('key :>> ', key);
}
2
3
4
5
6
7
8
9
Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。
# next 方法的参数
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
function* f() {
for(var i = 0; true; i++) {
console.log('start')
var reset = yield i;
console.log('reset=>>',reset)
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
第一步执行:执行console.log('start')
第二步执行:返回yield i;i此时为i = 0
g.next() // { value: 1, done: false }
第一步执行:console.log('reset=>>',reset)
第二步执行:i++
第三步执行:执行console.log('start')
第四步执行:返回yield i;i此时为i = 1
g.next(true) // { value: 0, done: false }
第一步执行:console.log('reset=>>',reset)
第二步执行:i = -1;
第三步执行:i++
第四步执行:执行console.log('start')
第五步执行:返回yield i;i此时为i = 0
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
Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')
2
3
4
5
6
7
8
9
10
11
12
13
14
function* foo(x) {
console.log('执行')
var y = 2 * (yield (x + 10) + 10);
console.log('y=>>>',y) // 200
var z = yield (y / 5);
console.log('z=>>>',z) // 10
return (x + y + z); // 310
}
var a = foo(100);
console.log(a.next()) // { value: 120, done: false }
第一步:console.log('执行')
第二步:yield (100 + 10) + 10
console.log(a.next(100)) // { value: 40, done: false } , 100作为 (yield (x + 10) + 10)的返回值,
第一步:console.log('y=>>>',y) // 所以打印100
第二步:yield (200 / 5);
console.log(a.next(10)) // { value: 310, done: true }, 10 作为 yield (y / 5)
第一步:console.log('z=>>>',z)
第二步:return (x + y + z);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# for...of 循环 Generator
for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
2
3
4
5
6
7
8
9
10
11
12
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
2
3
4
5
6
7
8
9
10
11
12
# yield* 表达式
如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
// 手动遍历 foo()
for (let i of foo()) {
console.log(i);
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
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
上面例子中, outer2使用了yield*,outer1没使用。结果就是,outer1返回一个遍历器对象,outer2返回该遍历器对象的内部值。
# 作为对象属性的 Generator 函数
如果一个对象的属性是 Generator 函数,可以简写成下面的形式
let obj = {
* myGeneratorMethod() {
···
}
};
2
3
4
5
# Generator 函数的this
Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'
2
3
4
5
6
7
8
9
10
# 含义
Generator 是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。
下面的 Generator 实现与 ES5 实现对比,可以看到少了用来保存状态的外部变量ticking,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator 之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。
var ticking = true;
var clock = function() {
if (ticking)
console.log('Tick!');
else
console.log('Tock!');
ticking = !ticking;
}
// Generator 实现
var clock = function* () {
while (true) {
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Generator 与上下文
JavaScript 代码运行时,会产生一个全局的上下文环境(context,又称运行环境),包含了当前所有的变量和对象。然后,执行函数(或块级代码)的时候,又会在当前上下文环境的上层,产生一个函数运行的上下文,变成当前(active)的上下文,由此形成一个上下文环境的堆栈(context stack)。
这个堆栈是“后进先出”的数据结构,最后产生的上下文环境首先执行完成,退出堆栈,然后再执行完成它下层的上下文,直至所有代码执行完成,堆栈清空。
Generator 函数不是这样,它执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,但是并不消失,里面的所有变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行。
function* gen() {
yield 1;
return 2;
}
let g = gen();
console.log(
g.next().value,
g.next().value,
);
2
3
4
5
6
7
8
9
10
11
12
13
上面代码中,第一次执行g.next()时,Generator 函数gen的上下文会加入堆栈,即开始运行gen内部的代码。等遇到yield 1时,gen上下文退出堆栈,内部状态冻结。第二次执行g.next()时,gen上下文重新加入堆栈,变成当前的上下文,重新恢复执行。
# 应用
# 异步操作的同步化表达
通过 Generator 函数部署 Ajax 操作,可以用同步的方式表达。
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
function* main() {
var result = yield request("https://api.github.com/");
console.log(result);
}
function request(url) {
$.ajax(url,{
success: function(res) {
it.next(res)
}
})
}
var it = main();
it.next();
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 控制流管理
如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
2
3
4
5
6
7
8
9
10
采用 Promise 改写上面的代码。
Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();
2
3
4
5
6
7
8
9
10
上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量 Promise 的语法。Generator 函数可以进一步改善代码运行流程。
注意:这种做法,只适合同步操作
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}
2
3
4
5
6
7
8
9
10
11
12
然后,使用一个函数,按次序自动执行所有步骤。
scheduler(longRunningTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value);
// 如果Generator函数未结束,就继续调用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}
2
3
4
5
6
7
8
9
10
# 部署 Iterator 接口
利用 Generator 函数,可以在任意对象上部署 Iterator 接口。
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 作为数据结构
# Generator 函数的异步应用
异步编程对 JavaScript 语言太重要。JavaScript 语言的执行环境是“单线程”的,如果没有异步编程,根本没法用,非卡死不可。本章主要介绍 Generator 函数如何完成异步操作。
ES6 诞生以前,异步编程的方法,大概有下面四种。
- 回调函数
- 事件监听
- 发布/订阅
- Promise 对象. 社区版
# Thunk 函数
编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
function f(m) {
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk) {
return thunk() * 2;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
上面代码中,函数 f 的参数x + 5被一个函数替换了。凡是用到原参数的地方,对Thunk函数求值即可。
这就是 Thunk 函数的定义,它是“传名调用”的一种实现策略,用来替换某个表达式。
# JavaScript 语言的 Thunk 函数
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);
// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
return function (callback) {
return fs.readFile(fileName, callback);
};
};
var readFileThunk = Thunk(fileName);
readFileThunk(callback);
2
3
4
5
6
7
8
9
10
11
12
13
上面代码中,fs模块的readFile方法是一个多参数函数,两个参数分别为文件名和回调函数。经过转换器处理,它变成了一个单参数函数,只接受回调函数作为参数。这个单参数版本,就叫做 Thunk 函数。
任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。
function Thunk(fn){
return function (){
var args = Array.prototype.slice.call(arguments);
return function (callback){
args.push(callback);
return fn.apply(this, args);
}
};
};
function Thunk(fn){
return function (...args){
return function (callback){
return fn.apply(this, [...args,callback]);
}
};
};
// 转换操作
var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);
function f(a, cb) {
cb(a);
}
const ft = Thunk(f);
ft(1)(console.log) // 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
TG大神写的thunkify (opens new window)函数
function thunkify(fn) {
return function(...args) {
const ctx = this
return function(done) {
let called
args.push(function() {
if(called) return
called = true
done.apply(null, arguments)
})
try {
fn.apply(ctx,args)
}catch(err) {
done(err)
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Tunckify函数使用方式
// 练习一
const fs = require('fs')
function read(err,data){
console.log(data.toString())
}
const readFile = thunkify(fs.readFile)
readFile('test.txt')(read)
// 练习二
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
var gen = function* (){
var r1 = yield readFileThunk('/etc/fstab');
console.log(r1.toString());
var r2 = yield readFileThunk('/etc/shells');
console.log(r2.toString());
};
var g = gen();
var r1 = g.next();
r1.value(function (err, data) {
if (err) throw err;
var r2 = g.next(data);
r2.value(function (err, data) {
if (err) throw err;
g.next(data);
});
});
// 练习三 ,自动执行thunk函数
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}
next();
}
var g = function* (){
var f1 = yield readFileThunk('fileA');
var f2 = yield readFileThunk('fileB');
// ...
var fn = yield readFileThunk('fileN');
};
run(g);
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
55
56
# Generator 函数的流程管理
Generator 函数可以自动执行。
function* gen() {
// ...
}
var g = gen();
var res = g.next();
while(!res.done){
console.log(res.value);
res = g.next();
}
2
3
4
5
6
7
8
9
10
11
# Proxy (opens new window)
<script>
var obj = [1, 2, 4, 5]
// 负的index
var proxy = new Proxy(obj, {
get(target, key, reciever) {
console.log('target', target);
console.log('key :>> ', key);
return target[target.length + key * 1]
}
})
// console.log(proxy[-1]);
// console.log(proxy[1]);
// console.log(proxy1);
// receiver 指向proxy2实例
var proxy2 = new Proxy({}, {
get: function(target, key, receiver) {
return receiver;
}
});
// const d = Object.create(proxy2);
// // d.a === d // true
// console.log('d :>> ', d);
var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;
var pipe = function(value) {
var funcStack = [];
var proxy = new Proxy({}, {
get(target,key,receiver) {
if(key === 'get') {
return funcStack.reduce((val, fn )=> {
return fn(val)
},value)
}
funcStack.push(window[key])
return proxy
}
})
return proxy
}
console.log(pipe(3).double.pow.reverseInt.get);;
</script>
<h1>Proxy的set配置</h1>
<p>接受4个参数 `target, key ,newValue, receiver`</p>
<h2>用set校验参数,age不能大于100,且是整数</h2>
<script>
var obj ={
age: 10
}
var proxy = new Proxy(obj, {
set(target, key, value, receiver) {
if(!Number.isInteger(value)) {
throw '请输入整数'
}
if(value > 100) {
throw 'age 不能大于 100'
}
return Reflect.set(target, key, value, receiver)
}
})
// proxy.age = 200
proxy.age = 20
</script>
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# Reflect
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。
- 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上
- 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
- 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
- Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
# async 滥用
三个函数相互依赖
DETAILS
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(ms)
}, ms);
});
}
async function asyncPrint(value, ms) {
console.time('ok')
const a = await timeout(1000);
console.log(a)
const b = await timeout(a + 1000);
console.log(b)
const c = await timeout(b+ 1000);
console.log(c)
console.log(value);
console.timeEnd('ok')
}
asyncPrint('hello world');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
三个函数不依赖,但是要串行
DETAILS
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(ms)
}, ms);
});
}
async function asyncPrint(value, ms) {
console.time('ok')
await timeout(1000);
await timeout(a + 1000);
await timeout(b+ 1000);
console.log(value);
console.timeEnd('ok')
}
asyncPrint('hello world');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
三个函数不依赖,可以并行
DETAILS
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(ms)
}, ms);
});
}
async function asyncPrint(value, ms) {
console.time('ok')
const t1 = timeout(1000);
const t2 = timeout(2000);
const t3 = timeout(3000);
const a = await t1
console.log(a)
const b = await t2
console.log(b)
const c = await t3
console.log(c)
console.log(value)
console.timeEnd('ok')
}
asyncPrint('hello world');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
forEach会让async函数并行
:::detais
function dbFuc(db) {
let docs = [{}, {}, {}];
docs.forEach(async function (doc) {
await db.post(doc);
});
}
2
3
4
5
6
:::
for循环会让函数串行
DETAILS
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
2
3
4
5
6
7
# Promise.resolve 和 new Promise
- https://www.zhihu.com/question/268007969/answer/339811998
- https://stackoverflow.com/questions/53894038/whats-the-difference-between-resolvethenable-and-resolvenon-thenable-object/53929252#53929252
- https://segmentfault.com/q/1010000016147496
- https://segmentfault.com/q/1010000016913023
- https://github.com/xianshenglu/blog/issues/60
← 进制 面向对象编程基本原则 →