JavaScript 提供对数据迭代的协议。迭代协议具体分为两个协议:
可迭代协议
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of
结构中,哪些值可以被遍历到。
要成为可迭代对象,该对象必须实现 @@iterator
方法,可通过常量 [Symbol.iterator]
访问该属性,它的返回值为一个符合迭代器协议的对象。
迭代器协议
[Symbol.iterator]
返回的对象就是一个迭代器对象。它提供 next(
) 方法,返回一个具有以下属性的对象:
value
:迭代器返回的任何 JavaScript 值。done 为 true 时可省略。done
:迭代器能否返回下一个值,不能则为 false
, 否则为 true
。我们可以这样理解,一个可迭代对象实现了一个接口,询问两个问题:
内置的可迭代对象
String
、Array
、TypedArray
、Map
、Set
都是内置的可迭代对象,因为它们的每个 prototype
对象都实现了 @@iterator
方法。generator
)返回生成器对象,它们是可迭代的迭代器。对象是否可迭代?
Object
是不可迭代的,如下面的代码:
const people = {
name: 'John Doe',
age: 13,
sex: 'male',
born: 1994
};
for (const p of people) {
console.log(p); // Uncaught TypeError: people is not iterable
}
我们可以给对象添加 Symbol.iterator
,使其可迭代,但这个不是最佳方案。我们可以使用 Object.entires()
或使用 Map。
Object.entires()
Object.entries(people); // [['name', 'John Doe'], ['age', 13], ['sex', 'male'], ['born', 1994]]
Map
const people = {
name: 'John Doe',
age: 13,
sex: 'male',
born: 1994
};
const buildMap = obj =>
Object.keys(obj).reduce((map, key) => map.set(key, obj[key]), new Map());
const map = buildMap(people);
map.get('name') // John Doe
map.get('age'); // 13
实现一个迭代器
由上可知,实现一个迭代器,我们要做两步:
class MyIterator {
constructor(params) {
this.index = 0;
this.value = params;
}
[Symbol.iterator]() {
return this; // A
}
next() { // B
return {
value: this.value[this.index++],
done: this.index > this.value.length ? true : false,
};
}
}
let it = new MyIterator(["a", "b", "c"]);
console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: "c", done: false }
console.log(it.next()); // { value: undefined, done: true }
console.log(it.next()); // { value: undefined, done: true }
小练习
手写一个迭代器。
答案:见上文。
常规函数只会返回一个单一值(或者不返回值),而 ES6 的generator
可以按需一个接一个地返回(yield
)多个值。
JavaScript 中的函数是不可暂停的,generator
函数则可以暂停:
function* generate() {
console.log("invoked 1st time"); // "invoked 1st time"
yield 1;
console.log("invoked 2nd time"); // // "invoked 2nd time"
yield 2;
}
let gen = generate();
console.log(gen); // Object [Generator] {}
let result = gen.next();
console.log("result", result); // {value: 1, done: false}
我们分析一下上面的代码:
generator
函数yield
表达式返回一个值,并且暂停这个函数的执行generate()
调用后,返回一个 Generator
对象,还没有执行它的函数体的部分。Generator
对象上的 next()
方法,执行函数体的内容,遇到 yield
返回 1
并且暂停 generator
函数。next() 方法
generator
的主要方法就是 next()
,它是一个具有两个属性的对象:
value:
产出的(yielded
)的值。done
: 如果 generator
函数已执行完成则为 true
,否则为 false
。yield
当调用 next()
方法的时候,yield 将返回值添加到对象的 value
属性上。
function* foo() {
yield 1;
yield 2;
yield 3;
}
let f = foo();
console.log(f.next()); // { value: 1, done: false }
yield*
yield*
表达式迭代操作数,并产生它返回的每个值。我们可以使用 yield*
返回数组的每个元素:
function* yieldArrayElements() {
yield 1;
yield* [ 20, 30, 40 ];
}
let a = yieldArrayElements();
console.log(a.next()); // { value: 1, done: false }
console.log(a.next()); // { value: 20, done: false }
console.log(a.next()); // { value: 30, done: false }
console.log(a.next()); // { value: 40, done: false }
generator
是可迭代的
ES6 中 for...of
可以遍历:
Array
、String
、Map
Array-like
对象,比如 arguments
或者 NodeList
let str = 'abc';
for (let c of str) {
console.log(c);
}
输出结果如下:
同样,我们也可以使用 for...of
循环遍历 generator
所有的值。如下示例:
function* generate() {
console.log("invoked 1st time"); // "invoked 1st time"
yield 1;
console.log("invoked 2nd time"); // "invoked 2nd time"
yield 2;
}
let gen = generate();
console.log(gen); // Object [Generator] {}
// for...of
for (const g of gen) {
console.log("for...of g", g);
}
输出如下图:
小练习
下面代码的输出结果是什么?
const arr = ['a', 'b', 'c'];
function* generator() {
yield 1;
yield* arr;
yield 2;
}
for (let value of generator()) {
console.log(value)
}
答案:这里的考点是 yield*
,会输出数组每个元素。
async/await
定义
async/await
实际上是 Promise 的语法糖,它可以用一种更舒适的方式使用 Promise,更易于理解和使用。
async
关键字放在函数前面,表达这个函数总是返回一个 Promise,其他值将自动被包装在一个 resolved
的 Promise 中。
async function f() {
return 1;
}
f().then((v) => console.log(v));
console.log('f', f())
结果如下图:
await
关键字可以暂停函数的执行,直到 Promise 的状态变成已敲定,然后 Promise 的结果继续执行。相比于 promise.then
,它显得更为优雅。如果在非 async
中使用,会报语法错误。
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // 等待,直到 promise resolve (*)
alert(result); // "done!"
}
f();
错误处理
如果一个 Promise 正常 resolve,await promise
的结果就是其返回值,否则,将抛出错误。我们可以使用 try/catch
来捕获错误。
async function f() {
try {
let response = await new Promise((resolve, reject) => {
throw new Error('test err')
});
} catch(err) {
console.log(err); // Error: test err
}
}
f();
小练习
如何使用 async
实现 delay
函数, delay
函数定义如下:
delay((str) => str, 3000, 'Hello world').then(res => console.log(res))
答案:参考代码如下:
const sleep = (cb, time, args) => ({
then: (cb) => {
setTimeout(() => cb(args), time);
}
});
const delay = async (cb, time, args) => {
return await sleep(cb, time, args);
};
delay((str) => str, 3000, "Hello world").then((res) => console.log(res)); // Hello world
红绿灯题目:实现一个信号灯,这个信号灯,有黄绿红,他们各自亮灯的持续时间是 1s,2s,3s 如此反复。
这道题考查的是对 Promise
和 async/await
的应用。
function delay(fn, time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(fn()); // A
}, time);
});
}
async function light() {
await delay(() => console.log("red"), 3000);
await delay(() => console.log("green"), 2000);
await delay(() => console.log("yellow"), 1000);
await light();
}
light();
light()
是一个带有 async
关键字的函数,代表这是个异步函数。await
函数接收一个 Promise
,并且等待 Promise resolve
之后再继续执行后续代码。delay()
函数是我们创建的一个返回 Promise 的函数,这个函数在指定时间后异步调用回调函数,注意代码 A 处.阅读量:1224
点赞量:0
收藏量:0