它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 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
複製代碼