它是一种接口,为各类不一样的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就能够完成遍历操做。javascript
Iterator 的遍历过程:java
next
方法每一次调用next
方法,都会返回数据结构的当前成员的信息。(返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。)编程
ES6 规定,一个数据结构只要具备Symbol.iterator
属性,就能够认为是“可遍历的”。Symbol.iterator
属性自己是一个函数,就是当前数据结构默认的遍历器生成函数。(Symbol)数组
Symbol.iterator
,它是一个表达式,返回Symbol
对象的iterator
属性,这是一个预约义好的、类型为Symbol
的特殊值。promise
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
}
class LinkedList {
[Symbol.iterator] () {
return {
next () {
return { value: 1, done: true }
}
}
}
}
复制代码
原生数据结构部署了遍历器接口有:Array Map Set String TypedArray 函数的 arguments 对象 NodeList 对象
。数据结构
只要部署了遍历器接口就可使用for...of
遍历。异步
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
复制代码
如下场景会调用 Iterator 接口async
let [first, ...rest] = [1,2,3] // 解构赋值
[...'hi'] // 扩展运算符
let generator = function* () {
yield* [1,2,3] // yield*
};
// for...of
// Array.from()
// Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]]))
// Promise.all() Promise.race()
复制代码
遍历器返回对象除了next
方法还有return
和throw
方法,它们是可选的。异步编程
return
方法的使用场合是,若是for...o
f循环提早退出(一般是由于出错,或者有break
语句),就会调用return
方法。return
方法必须返回一个对象。函数
Generator 函数是 ES6 提供的一种异步编程解决方案,执行 Generator 函数会返回一个遍历器对象。
Generator 函数使用function*
定义(有没有空格都行),内部可使用yield
和yield*
表达式。
function* g() {
yield 1
yield 2
return 3
yield 4
}
let a = g()
// 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,遍历器对象
console.log(a)
/* __proto__: Generator __proto__: Generator constructor: GeneratorFunction {prototype: Generator, constructor: ƒ, Symbol(Symbol.toStringTag): "GeneratorFunction"} next: ƒ next() return: ƒ return() throw: ƒ throw() Symbol(Symbol.toStringTag): "Generator" __proto__: Object [[GeneratorLocation]]: VM89:1 [[GeneratorStatus]]: "suspended" [[GeneratorFunction]]: ƒ* g() [[GeneratorReceiver]]: Window [[Scopes]]: Scopes[2] */
// 必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。
a.next() // { value: 1, done: false }
a.next() // { value: 2, done: false }
a.next() // { value: 3, done: true }
a.next() // { value: undefined, done: true }
复制代码
Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法能够恢复执行,当碰到yield
就返回yield
后面的值和done
为false
,若是遇到return
就返回return
后的值和done
为true
的对象,若是没有碰到yield
和return
则返回值为undefined
和done
为true
的对象。
yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,所以等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
yield
表达式若是用在另外一个表达式之中,必须放在圆括号里面,表达式用做函数参数或放在赋值表达式的右边,能够不加括号。
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
复制代码
任意一个对象的Symbol.iterator
方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象,能够把 Generator 赋值给对象的Symbol.iterator
属性,从而使得该对象具备 Iterator 接口。
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
复制代码
yield
能够有返回值,它经过next
方法传入。
function* g() {
let v1 = yield
console.log(v1)
let v2 = yield 2
console.log(v2)
}
let a = g()
a.next() // { value: undefined, done: false }
a.next([1,2,3]) // { value: 2, done: false }
a.next() // { value: undefined, done: true }
// [1,2,3]
// undefined
复制代码
上面的例子,第一个next
方法执行到第一个yield
暂停,执行第二次next
方法时,咱们用[1,2,3]
作为参数,这时第一个yield
就返回[1,2,3]
,因此第一次打印[1,2,3]
第二次打印undefined
。
因此对第一个next
方法传递参数是没有用的,第二个next
的参数才做为第一个yield
的返回值。
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);
}
// 1 2 3 4 5
复制代码
只要done
为true
,for...of
就会终止循环,因此return
的返回值没有打印。
Generator 函数原型上有throw
方法,能够用来在函数体外抛出错误,而后在 Generator 函数体内捕获。
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
};
var i = g();
i.next();
try {
i.throw('a'); // throw 方法能够接受一个参数,该参数会被catch语句接收
i.throw('b'); // throw 方法会附带执行一次 next 方法
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
复制代码
若是内部没有捕获,那么错误就会跑到外面来,被外部捕获。若是内外都没有捕获错误,那么程序将报错,直接中断执行。
在使用throw
以前,必须执行一次next
方法,不然抛出的错误不会被内部捕获,而是直接在外部抛出,致使程序出错。
函数体内的错误能够被外部捕获。
function* g() {
yield 1
yield 2
throw new Error('err')
yield 3
}
let a = g()
try {
console.log(a.next()) // { value: 1, done: tfalse }
console.log(a.next()) // { value: 2, done: false }
console.log(a.next()) // 报错
} catch(e) {}
console.log(a.next()) // { value: undefined, done: true }
复制代码
只要内部错误没有捕获,跑到外面来,那么迭代器就会自动中止。
Generator 函数返回的遍历器对象,还有一个return
方法,能够返回给定的值,而且终结遍历 Generator 函数。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
// ---------------
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
// 若是 Generator 函数内部有try...finally代码块,且正在执行try代码块,那么return方法会推迟到finally代码块执行完再执行
复制代码
yield*
表达式,用来在一个 Generator 函数内部,调用另外一个 Generator 函数。
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
function* g() {
yield 1
yield 2
return 3
}
function* e() {
let returnValue = yield* g()
console.log(returnValue) // 3
// 若是另外一个函数带有 return 则须要本身获取
yield* ['a', 'b', 'c'] // 还能够是 数组,字符串这些原生带有迭代器的对象
}
复制代码
若是yield
表达式后面跟的是一个遍历器对象,须要在yield
表达式后面加上星号,代表它返回的是一个遍历器对象。
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i=0; i < tree.length; i++) {
yield* iterTree(tree[i]);
}
} else {
yield tree;
}
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];
[...iterTree(tree)]
复制代码
yield*
能够轻松的抹平数组。
Generator 函数不能和new
命令一块儿用。
ES2017 标准引入了 async 函数,使得异步操做变得更加方便。它能够很是清晰的将异步操做写成同步操做。
let p = async function getData() {
let data = await db.getData()
console.log(data)
}
console.log(p)
/* Promise {<resolved>: undefined} __proto__: Promise [[PromiseStatus]]: "resolved" [[PromiseValue]]: undefined */
let b = async function () {} // 函数表达式
let c = async () => {} // 箭头函数
复制代码
async
函数 以async
开头,其内部可使用await
等待异步操做完成。async
函数会返回一个Promise
对象,因此可使用then方法添加回调函数。
函数执行的时候,一旦遇到await
就会先返回,等到异步操做完成,再接着执行函数体内后面的语句。
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数,async
函数内部抛出错误,会致使返回的 Promise
对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到。
async function a() {
throw new Error('err')
}
a() // Uncaught (in promise) Error 报错,但不会终止程序
console.log(123) // 正常执行
复制代码
async
函数返回的Promise
对象,必须等到内部全部await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。
await
命令后面通常是一个 Promise 对象,返回该对象的结果。若是不是 Promise 对象,就直接返回对应的值。await
命令后面是一个thenable
对象(即定义then方法的对象),那么await
会将其等同于 Promise 对象。await
语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
} // 咱们能够将多个 await 命令放入 try-catch 中
复制代码
async
函数其实它就是 Generator 函数的语法糖。
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) { // 返回一个 Promise 对象
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e); // 执行 fn 中的代码,若是出错直接 reject 返回的 Promise
}
if(next.done) { // 若是 fn 执行完成则 resolve fn return 的值
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
// 将 yield 返回的值变成 Promise 并执行它的 then 方法
step(function() { return gen.next(v); });
// 当 yield 后面的 Promise 执行成功完成时则继续执行 fn 函数
// 并将它产生的值传入 fn 函数
}, function(e) {
step(function() { return gen.throw(e); });
// 若是出现错误则将错误传入 fn 内部。
// 若是内部没有捕获则被本函数上面的 try-catch 捕获
});
}
step(function() { return gen.next(undefined); });
});
}
复制代码
异步遍历器和遍历器的区别在于,异步遍历器返回的是一个 Promise 对象,可是它的值的格式和遍历器同样。异步遍历器接口,部署在Symbol.asyncIterator
属性上面。
异步遍历器它的next
不用等到上一个 Promise resolve 了才能调用。这种状况下,next
方法会累积起来,自动按照每一步的顺序运行下去。
const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
const [{value: v1}, {value: v2}] = await Promise.all([
asyncIterator.next(), asyncIterator.next()
]);
console.log(v1, v2); // a b
复制代码
for await...of
循环,是用于遍历异步的 Iterator 接口。
async function f() {
for await (const x of createAsyncIterable(['a', 'b'])) {
console.log(x);
}
}
// a
// b
复制代码
若是next
方法返回的 Promise 对象被reject
,for await...of
就会报错,要用try...catch
捕捉。
它也能够用于同步遍历器。
就像 Generator 函数返回一个同步遍历器对象同样,异步 Generator 函数的做用,是返回一个异步遍历器对象。
async function* gen() {
yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }
// 同步 Generator 函数
function* map(iterable, func) {
const iter = iterable[Symbol.iterator]();
while (true) {
const {value, done} = iter.next();
if (done) break;
yield func(value);
}
}
// 异步 Generator 函数
async function* map(iterable, func) {
const iter = iterable[Symbol.asyncIterator]();
while (true) {
const {value, done} = await iter.next();
if (done) break;
yield func(value);
}
}
复制代码
Generator 函数处理同步操做和异步操做时,可以使用同一套接口。异步 Generator 函数内部,可以同时使用await
和yield
命令。
若是异步 Generator 函数抛出错误,会致使 Promise 对象的状态变为reject
,而后抛出的错误被catch
方法捕获。
yield*语句也能够跟一个异步遍历器。
async function* gen1() {
yield 'a';
yield 'b';
return 2;
}
async function* gen2() {
// result 最终会等于 2
const result = yield* gen1();
}
for await (const x of gen2()) {
console.log(x);
}
// a
// b
复制代码