ES6
經常使用但被忽略的方法 系列文章,整理做者認爲一些平常開發可能會用到的一些方法、使用技巧和一些應用場景,細節深刻請查看相關內容鏈接,歡迎補充交流。Generator
函數是 ES6
提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣。語法上,Generator
函數是一個狀態機,封裝了多個內部狀態。執行 Generator
函數會返回一個遍歷器對象,能夠依次遍歷 Generator
函數內部的每個狀態。function
關鍵字與函數名之間有一個星號;yield
表達式,定義不一樣的內部狀態。function* detanxGenerator() {
yield 'detanx';
return 'ending';
}
const dg = detanxGenerator();
複製代碼
dg.next() // { value: 'detanx', done: false }
dg.next() // { value: 'ending', done: true }
dg.next() // { value: undefined, done: true }
複製代碼
Generator
函數開始執行,直到遇到第一個yield
表達式爲止。next
方法返回一個對象,它的value
屬性就是當前yield
表達式的值hello
,done
屬性的值false
,表示遍歷尚未結束。Generator
函數從上次yield表達式停下的地方,一直執行到return
語句(若是沒有return
語句,就執行到函數結束)。done
屬性的值true
,表示遍歷已經結束。Generator
函數已經運行完畢,next
方法返回對象的value
屬性爲undefined
,done
屬性爲true
。之後再調用next
方法,返回的都是這個值。function
關鍵字與函數名之間的*
未規定,因此有不少寫法,咱們寫得時候最好仍是使用第一種,即*
緊跟着function
關鍵字後面,*
後面再加一個空格。function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
function *foo(x, y) { ··· }
function * foo(x, y) { ··· }
複製代碼
Generator
函數返回的遍歷器對象,只有調用next
方法纔會遍歷下一個內部狀態,因此其實提供了一種能夠暫停執行的函數。yield
表達式就是暫停標誌。next
方法的運行邏輯yield
表達式,就暫停執行後面的操做,並將緊跟在yield
後面的那個表達式的值,做爲返回的對象的value
屬性值。next
方法時,再繼續往下執行,直到遇到下一個yield
表達式。yield
表達式,就一直運行到函數結束,直到return
語句爲止,並將return
語句後面的表達式的值,做爲返回的對象的value
屬性值。return
語句,則返回的對象的value
屬性值爲undefined
。yield
表達式後面的表達式,只有當調用next
方法、內部指針指向該語句時纔會執行。下面的123 + 456
不會當即求值,只有當執行next
到對應的yield
表達式纔會求值。function* gen() {
yield 123 + 456;
}
複製代碼
yield
表達式只能用在 Generator
函數裏面,用在其餘地方都會報錯。(function (){
yield 1;
})()
// SyntaxError: Unexpected number
複製代碼
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
}
複製代碼
Iterator
接口的關係Symbol.iterator
方法,等於該對象的遍歷器生成函數,調用該函數會返回該對象的一個遍歷器對象。Generator
函數就是遍歷器生成函數,能夠把 Generator
賦值給對象的Symbol.iterator
屬性,從而使得該對象具備 Iterator
接口。var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
複製代碼
Generator
函數執行後,返回一個遍歷器對象。該對象自己也具備Symbol.iterator
屬性,執行後返回自身。function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
複製代碼
for...of
循環for...of
循環能夠自動遍歷 Generator
函數運行時生成的Iterator
對象,且此時再也不須要調用next
方法。 一旦next
方法的返回對象的done
屬性爲true
,for...of
循環就會停止,且不包含該返回對象。function* numbers() {
yield 1;
yield 2;
return 3;
}
for (let v of numbers()) {
console.log(v);
}
// 1 2
複製代碼
for...of
循環...
)、解構賦值和Array.from
方法內部調用的,都是遍歷器接口。它們均可以將 Generator
函數返回的 Iterator
對象,做爲參數。// 擴展運算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解構賦值
let [x, y] = numbers();
x // 1
y // 2
複製代碼
yield
表達式自己沒有返回值,或者說老是返回undefined
。next
方法能夠帶一個參數,該參數就會被看成上一個yield
表達式的返回值。function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
複製代碼
上面代碼中,第二次運行next
方法的時候不帶參數,致使 y
的值等於2 * undefined
(即NaN
),除以 3
之後仍是NaN
,所以返回對象的value
屬性也等於NaN
。第三次運行next
方法的時候不帶參數,因此z
等於undefined
,返回對象的value
屬性等於5 + NaN + undefined
,即NaN
。node
若是向next
方法提供參數,返回結果就徹底不同了。上面代碼第一次調用b的next
方法時,返回x+1
的值6
;第二次調用next
方法,將上一次yield
表達式的值設爲12
,所以y
等於24
,返回y / 3
的值8
;第三次調用next
方法,將上一次yield
表達式的值設爲13
,所以z
等於13
,這時x
等於5
,y
等於24
,因此return
語句的值等於42
。es6
因爲next
方法的參數表示上一個yield
表達式的返回值,因此在第一次使用next
方法時,傳遞參數是無效的。算法
Generator
函數恢復執行,而且使用不一樣的語句替換yield
表達式。next()
是將上一個yield
表達式替換成一個值。const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 至關於將 let result = yield x + y
// 替換成 let result = 1;
複製代碼
throw()
是將yield
表達式替換成一個throw
語句。gen.throw(new Error('出錯了')); // Uncaught Error: 出錯了
// 至關於將 let result = yield x + y
// 替換成 let result = throw(new Error('出錯了'));
複製代碼
return()
是將yield
表達式替換成一個return
語句。gen.return(2); // Object {value: 2, done: true}
// 至關於將 let result = yield x + y
// 替換成 let result = return 2;
複製代碼
Generator.prototype.throw()
Generator
函數返回的遍歷器對象,throw
方法能夠在函數體外拋出錯誤,而後在 Generator
函數體內捕獲。throw
方法能夠接受一個參數,該參數會被catch
語句接收,建議拋出Error
對象的實例。var g = function* () {
try {
yield;
} catch (e) {
console.log(e);
}
};
var i = g();
i.next();
i.throw(new Error('出錯了!'));
// Error: 出錯了!(…)
複製代碼
throw
方法和全局的throw
命令。上面代碼的錯誤,是用遍歷器對象的throw
方法拋出的,而不是用throw
命令拋出的。後者只能被函數體外的catch
語句捕獲。Generator
函數內部沒有部署try...catch
代碼塊,那麼throw
方法拋出的錯誤,將被外部try...catch
代碼塊捕獲。var g = function* () {
while (true) {
yield;
console.log('內部捕獲', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕獲', e);
}
// 外部捕獲 a
複製代碼
Generator
函數內部和外部,都沒有部署try...catch
代碼塊,那麼程序將報錯,直接中斷執行。var gen = function* gen(){
yield console.log('hello');
yield console.log('world');
}
var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined
複製代碼
throw
方法拋出的錯誤要被內部捕獲,前提是必須至少執行過一次next
方法。 g.throw(1)
執行時,next
方法一次都沒有執行過。這時,拋出的錯誤不會被內部捕獲,而是直接在外部拋出,致使程序出錯。function* gen() {
try {
yield 1;
} catch (e) {
console.log('內部捕獲');
}
}
var g = gen();
g.throw(1);
// Uncaught 1
複製代碼
Generator
函數體外拋出的錯誤,能夠在函數體內捕獲;反過來,Generator
函數體內拋出的錯誤,也能夠被函數體外的catch
捕獲。function* foo() {
var x = yield 3;
var y = x.toUpperCase();
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next(42);
} catch (err) {
console.log(err);
}
複製代碼
Generator.prototype.return()
Generator
函數返回的遍歷器對象,return
方法能夠返回給定的值,而且終結遍歷 Generator
函數。return
方法調用時,不提供參數,則返回值的value
屬性爲undefined
。function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return() // { value: undefined, done: true }
複製代碼
Generator
函數內部有try...finally
代碼塊,且正在執行try
代碼塊,那麼return
方法會致使馬上進入finally
代碼塊,執行完之後,整個函數纔會結束。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 }
複製代碼
yield*
表達式yield*
表達式用來在一個 Generator
函數裏面執行另外一個 Generator
函數。function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
複製代碼
yield*
後面跟着一個數組,因爲數組原生支持遍歷器,所以就會遍歷數組成員。yield
命令後面若是不加星號,返回的是整個數組,加了星號就表示返回的是數組的遍歷器對象。function* gen(){
yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }
複製代碼
Iterator
接口,就能夠被yield*
遍歷。let read = (function* () {
yield 'hello';
yield* 'hello';
})();
read.next().value // "hello"
read.next().value // "h"
複製代碼
Generator
函數有return
語句,那麼就能夠向代理它的 Generator
函數返回數據。下面例子中函數foo
的return
語句,向函數bar
提供了返回值。function* foo() {
yield 2;
yield 3;
return "foo";
}
function* bar() {
yield 1;
var v = yield* foo();
console.log("v: " + v);
yield 4;
}
var it = bar();
it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}
複製代碼
let obj = {
* myGeneratorMethod() {
···
}
};
// 等價
let obj = {
myGeneratorMethod: function* () { // *位置能夠在function關鍵字和括號之間任意位置
// ···
}
};
複製代碼
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'
複製代碼
Generator
函數g
返回的遍歷器obj
,是g
的實例,並且繼承了g.prototype
。可是,把g
看成普通的構造函數,並不會生效,由於g
返回的老是遍歷器對象,而不是this
對象。Generator
函數也不能跟new
命令一塊兒用,會報錯。function* F() {
yield this.x = 2;
yield this.y = 3;
}
new F()
// TypeError: F is not a constructor
複製代碼
call
方法綁定 Generator
函數內部的this
。構造函數調用之後,這個空對象就是 Generator
函數的實例對象。function* F() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var obj = {};
var f = F.call(obj);
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
obj.a // 1
obj.b // 2
obj.c // 3
複製代碼
f
,可是生成的對象實例是obj
,將obj
換成F.prototype
。再將F改爲構造函數,就能夠對它執行new
命令了。function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
複製代碼
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'] ];
for(let x of iterTree(tree)) {
console.log(x);
}
// a
// b
// c
// d
// e
複製代碼
// 下面是二叉樹的構造函數,
// 三個參數分別是左樹、當前節點和右樹
function Tree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
// 下面是中序(inorder)遍歷函數。
// 因爲返回的是一個遍歷器,因此要用generator函數。
// 函數體內採用遞歸算法,因此左樹和右樹要用yield*遍歷
function* inorder(t) {
if (t) {
yield* inorder(t.left);
yield t.label;
yield* inorder(t.right);
}
}
// 下面生成二叉樹
function make(array) {
// 判斷是否爲葉節點
if (array.length == 1) return new Tree(null, array[0], null);
return new Tree(make(array[0]), array[1], make(array[2]));
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// 遍歷二叉樹
var result = [];
for (let node of inorder(tree)) {
result.push(node);
}
result
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']
複製代碼
Ajax
的異步操做
Generator
函數部署 Ajax
操做,能夠用同步的方式表達。注意,makeAjaxCall
函數中的next
方法,必須加上response
參數,由於yield
表達式,自己是沒有值的,老是等於undefined
。function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}
var it = main();
it.next();
複製代碼
function* numbers() {
let file = new FileReader("numbers.txt");
try {
while(!file.eof) {
yield parseInt(file.readLine(), 10);
}
} finally {
file.close();
}
}
複製代碼
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
複製代碼
for...of
循環會自動依次執行yield
命令的特性,提供一種更通常的控制流管理的方法。let steps = [step1Func, step2Func, step3Func];
function* iterateSteps(steps){
for (var i=0; i< steps.length; i++){
var step = steps[i];
yield step();
}
}
複製代碼
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);
}
// foo 3
// bar 7
複製代碼
function* doStuff() {
yield fs.readFile.bind(null, 'hello.txt');
yield fs.readFile.bind(null, 'world.txt');
yield fs.readFile.bind(null, 'and-such.txt');
}
for (task of doStuff()) {
// task是一個函數,能夠像回調函數那樣使用它
}
複製代碼