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是一个函数,能够像回调函数那样使用它
}
复制代码