ES6笔记

来源:互联网 发布:市场数据分析报告 编辑:程序博客网 时间:2024/06/05 09:44

Generator 是一个分步执行的函数,可以根据需要执行下一步。合理利用这个特性可以很好解决异步编程回调嵌套的问题。

简介

Generator很像是一个函数,但是你可以暂停它的执行。你可以向它请求一个值,于是它为你提供了一个值,但是余下的函数不会自动向下执行直到你再次向它请求一个值。

特征

  • function 后面跟 *,函数体内有 yield 关键字
  • 执行Genertor函数返回一个遍历器对象,遇到 yield 暂停执行,调用 next 继续执行
  • next 方法返回的对象中 value 表示当前指针对应的yield语句的返回值,done 表示遍历是否结束
  • 当遍历结束之后,重复调用都只会返回 {value: undefined, done: true}
  • 如果函数有 return ,则返回 return 后面表达式的值做为 value的值,如果没有,则返回undefined 做为 value 的值
function* fn1(){
yield ‘a’
yield ‘b’
return ‘c’;
}
var g1 = fn1();
g1.next();//{value: “a”, done: false}
g1.next();//{value: “b”, done: false}
g1.next();//{value: “c”, done: false}
g1.next();//{value: undefined, done: true}

多维数组扁平化

var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a){
var len = a.length, item;
for(var i=0; i<len; i++){
item = a[i];
if(typeof item !== 'number'){
yield* flat(item);
}else{
yield item;
}
}
}
var g2 = flat(arr);
g2.next()//{value: 1, done: false}
for(var f of g2){
console.log(f)
}
// 2,3,4,5,6

Iterator接口

任意一个对象的 Symbol.iterator 方法,等于该对象的遍历器生成函数
将 Generator 函数赋值给对象的 Symbol.iterator 属性,可使对象具有Iterator接口
具有Iterator接口的对象可使用 ... 延展符展开

var k = {};
k[Symbol.iterator] = function* (){
yield 1;
yield 2;
}
[...k] //[1, 2]

Generator 函数返回的遍历器对象也有Symbol.iterator对象,执行后返回自身

var m = function* (){}
var g3 = m();
g3[Symbol.iterator]() == g3 //true

yield 语句本身没有返回值,或者永远返回 undefined
next 方法有参数时,参数将会被当然上一个 yield 语句的返回值

function* f() {
for(var i=0; true; i++) {
//这里,如果next没有传参数过来,reset将一直是undefined
var reset = yield i;
console.log(typeof reset)
if(reset) { i = -1; }
}
}
var g = f();
g.next(); //{value: 0, done: false}
g.next(); //undefined {value: 1, done: false}
g.next(true); //boolean {value: 0, done: false}

Generator函数在运行时,内部的 上下文(context )是保持不变的,但可以通过 next 方法不但的向函数体注入参数,从而改变函数的行为。
next 方法第一次调用时的参数会被忽略,只有后面的传参会被使用,第一个next方法可视作是启动遍历器对象

function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
//这里,由于yield本身的返回值是undefined,所以导致后面的计算都为NaN
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}
//这里,第二次的next传递了参数12,y=24, z=24/3=>8
//第三次 x=5 z=13 y=24 故x+y+z = 42
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

for...of 遍历

可以使用 for...of 遍历Generator 对象,不需要调用 next

function* fn(){
yield 1;
yield 2;
yield 3;
return 4;
}
for(var i of fn()){
console.log(i)
}
//1,2,3

由于 for...of 遍历时,当返回的对象 done 为 true 时就终止了循环,且不返回对象的 value, 所以最后的 4 没有输出,执行到 return 返回的是 {value: 4, done: true}

例1:利用Generator函数和for…of循环,实现斐波那契数列

function* fibonacci(){
let [prev, curr] = [0, 1];
for(;;){
[prev, curr] = [curr, prev+curr];
yield curr;
}
}
for(var y of fibonacci()){
if(y>50) break;
console.log(y);
}
// 1, 2, 3, 5, 8, 13, 21, 24

例2:给原生对象添加Iterator遍历接口

function* objectEntries(){
let keys = Object.keys(this);
for( let key of keys){
yield [key, this[key]]
}
}
var obj = {name: 'jack', age: 12, city:'hangzhou'}
obj[Symbol.iterator] = objectEntries;
for(let [key, value] of obj){
console.log(`${key}: ${value}`)
}
//name: jack
//age: 12
//city: hangzhou

for...of循环 / 扩展运算符... / 解构赋值 / Array.from 都可以操作Generator函数返回的Iterator对象

function* numbers(){
yield 1;
yield 2;
return 3;
yield 4;
}
[...numbers()]
//[1, 2]
Array.from(numbers())
//[1, 2]
let [x, y] = numbers()
//[1, 2]
for(let n of numbers()){
console.log(n)
}
//1, 2

Generator.prototype.throw

Generator函数内部部署 try...catch 可以对多个 yield 语句进行错误捕捉
Generator函数内部抛出错误可以被函数体外catch捕获
Generator函数体外抛出错误可以被函数体内catch捕获

function* fnA(){
let x = 1;
try{
yield console.log('a')
yield console.log('b')
//这里报错会中断后面的执行,返回{value: undefined, done: true}
yield x.toUppercase()
yield console.log('d')
}catch(e){
console.log('内:', e)
throw e
}
}
var ga = fnA()
try{
ga.next();
//外面的错误会被内部catch捕获
ga.throw('在函数体外部抛出的错误')
}catch(e){
console.log('外:', e)
}
//由于内部中断了执行,再调用next只会拿到{value: undefined, done: true}的结果
ga.next()
//输出结果:
//a
//内: 在函数体外部抛出的错误
//外: 在函数体外部抛出的错误
//{value: undefined, done: true}

如果Generator函数返回的Iterator对象在外部抛出了错误,而函数体内部又没有捕获,那么这个错误将抛给全局,程序中断

var gen = function* gen(){
yield console.log('hello');
yield console.log('world');
}
var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined 报错

Generator函数返回的Iterator对象throw一个错误后,会同时调用一次next方法

var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c

Generator.prototype.return

遍历器对象调用return可以改变返回的结果的vaule,并结束遍历返回的done为true
如果Generator函数内部有try…finally代码块,那么return方法会推迟到finally代码块执行完再执行

function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers()
g.next() // { done: false, value: 1 }
g.next() // { done: false, value: 2 }
g.return(7) // { done: false, value: 4 }
g.next() // { done: false, value: 5 }
g.next() // { done: true, value: 7 }

yield*

用于在一个Generator函数里面执行另一个Generator函数

function* fn1(){
yield 4;
yield 5;
}
function* fn2(){
yield 1;
yield 2;
yield 3;
yield* fn1();
yield 6;
}
function* fn3(){
yield 1;
yield 2;
yield fn1();
yield 6;
}
[...fn2()] //[1, 2, 3, 4, 5, 6] 将fn1中的内容展开了
[...fn3()] //[1, 2, fn1, 6] fn1为Generator返回的iterator对象

yield* 在Generator函数中可以遍历任何具有Iterator接口的对象

function* fn4(){
yield* [1,3,4]
yield* 'Hllo'
}
[...fn4()]//[1, 3, 4, "H", "l", "l", "o"]

yield 后面的Generator函数中可以通过 return 返回数据给 yield 所在的Generator函数

function* fn5(){
yield 1;
yield 2;
return 3;
}
function* fn6(){
yield 4;
var res = yield* fn5();
yield res;
yield 5;
}
[...fn6()] //[4, 1, 2, 3, 5]

多维数组转一维数组

function* flatArr(arr){
if(Array.isArray(arr)){
for(var k = 0; k<arr.length; k++){
yield* flatArr(arr[k])
}
}else{
yield arr;
}
}
var arr1 = [1,[2,3,[4,5,6]],7,[8,9]]
for(var i of flatArr(arr1)){
console.log(i)
}
// 1,2,3,4,5,6,7,8,9

二叉树遍历

function Tree(left, label, right){
this.left = left;
this.label = label;
this.right = right;
}
function make(arr){
if(arr.length === 1) return new Tree(null, arr[0], null);
return new Tree(make(arr[0]), arr[1], make(arr[2]))
}
function* inorder(t){
if(t){
yield* inorder(t.left)
yield t.label
yield* inorder(t.right)
}
}
var tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
var res = [];
for(var k in inorder(tree)){
res.push(k)
}
console.log(res)//["a", "b", "c", "d", "e", "f", "g"]

做为对象属性

var obj = {
*fn(){
yield 1;
yield 2;
}
}
//等同于
var obj2 = {
fn: function* (){
yield 1;
yield 2;
}
}
[...obj.fn()]//[1, 2]
[...obj2.fn()]//[1, 2]

状态保持

function* tick(){
while(true){
yield 'Tick'
yield 'Tock'
}
}
var t = tick();
t.next();
t.next();
t.next();
t.next();

应用场景

异步操作同步化

使用嵌套回调

function asyncFn1(done){
setTimeout(()=>{
console.log(1);
done(1);
}, 1500);
}
function asyncFn2(done){
setTimeout(()=>{
console.log(2);
done(2);
}, 2000);
}
function asyncFn3(done){
setTimeout(()=>{
console.log(3);
done();
}, 2500);
}
asyncFn1(function(args1){
asyncFn2(function(args2){
asyncFn3(function(args3){
//...
})
})
})

使用Generator封装,可以简化回调,让异步写起来更像是同步

//使用done回调保证任务按顺序执行
function asyncFn1(done){
setTimeout(()=>done(1), 1500);
}
function asyncFn2(done){
setTimeout(()=>done(2), 2000);
}
function asyncFn3(done){
setTimeout(()=>done(3), 2500);
}
//co的简单实现
function co(task){
var gen = task();
next();
function next(res){
var ret;
ret = gen.next(res);
if(ret.done) return;
if(typeof ret.value === 'function'){
ret.value(function(){
//依次遍历执行,如果没遍历完成,则递归调用
next.apply(this, arguments)
})
return;
}
}
}
co(function* task(){
try{
var res1 = yield asyncFn1;//第1次 next fn1返回结果1
console.log(res1) //第2次 next 传递fn1的结果 1
var res2 = yield asyncFn2;
console.log(res2) //第3次 next 传递fn2的结果 2
var res3 = yield asyncFn3;
console.log(res3) //第4次 next 传递fn3的结果 3
}catch(e){
// ...
}
})
// 1, 2, 3

任务流程管理

依次执行数组中的步骤

var step1 = () => 1;
var step2 = () => 2;
var step3 = () => 3;
var steps = [ step1, step2, step3 ]
function* iterateSteps(steps){
for(var step of steps){
yield step()
}
}
[...iterateSteps(steps)] // [1, 2, 4]

将多个步骤组合成多个任务,并依次执行这多个任务

var step1 = () => 1;
var step2 = () => 2;
var step3 = () => 3;
var step4 = () => 4;
var step5 = () => 5;
var step6 = () => 6;
var step7 = () => 7;
var step8 = () => 8;
var job1 = [ step1, step2, step3 ]
var job2 = [ step4, step5, step6 ]
var job3 = [ step7, step8 ]
var jobs = [job1, job2, job3]
function* iterateSteps(steps){
for(var step of steps){
yield step()
}
}
function* iterateJobs(jobs){
for(var job of jobs){
yield* iterateSteps(job)
}
}
[...iterateJobs(jobs)] //[1, 2, 3, 4, 5, 6, 7, 8]

部署Iterator接口

给任意对象部署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);
}

作为类数据结构

function doStuff(){
return [1, 2, 3]
}
for(var i of doStuff()){
console.log(i)
}
// 1, 2, 3
function* genStuff(){
yield 1;
yield 2;
yield 3;
}
for(var i of genStuff()){
console.log(i)
}
// 1, 2, 3