es6 之 Generator(一)
来源:互联网 发布:晒书房 知乎 编辑:程序博客网 时间:2024/05/24 07:34
es6 之 Generator(一)
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
第一次接触这个函数的时候,第一印象是,这个单词咋读…
generator 美[ˈdʒɛnəˌretɚ]
然后是,这个函数是干啥的,好难理解啊,function*
是啥啊,咋还有个*
,咋看着像指针呢…看了好多次阮一峰老师的ECMAScript 6 入门,总算有了一些理解。
1.基本使用
来看一看基本使用吧(function后的*前后可以加空格,但是推荐下面的写法)
function* stateMachine() { yield 'hello'; yield 'world'; return 'ending'; yield 'haha'; }
以上函数定义了一个名为stateMachine的Generator函数,定义了两个内部状态,先产出’hello’,再产出’world’,最后执行’ending’。(return)后的(yield)不执行
来执行以下
const result = stateMachine(); result.next(); // {value: "hello", done: false} result.next(); // {value: "world", done: false} result.next(); // {value: "ending", done: true} result.next(); // {value: undefined, done: true}
Generator内部封装了多个状态。程序员可以自主的选择”产出”状态的时机,这里的”产出”也就是(yield),
但Generator不能够无序的”产出”,此时(done)的值为(false),只能够按照设定的顺序,依次输出,也不能够进行”回溯”,当函数走到终点,
只能执行(return),(done)属性的值也为(true)。此时再执行next(),得到的value永远就是undefined了。(done)也为(true)。(done)表示的就是程序走到了尽头。
Generator函数返回的结果是一个迭代器对象,他是一个指向内部状态的指针对象。这和Java中的迭代器的使用非常类似。
执行next()方法进行遍历。每次执行,指针指向下一个状态,yield所标注的位置就是下一个产出的位置,函数执行完成后,next()的返回值永远就是{value: undefined, done: true}
2.next 方法
next(n) n会被当做上一个yield表达式的返回值
这句话是什么意思,乍一看确实很难理解,来看一个例子
function* f() { for(var i = 0; true; i++) { var reset = yield i; if(reset) { i = -1; } } } var g = f(); g.next() // { value: 0, done: false } g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false }
我们来仔细分析一下next方法的执行顺序:
1. 1.先找到函数中的第一个yield关键字,next方法即刻返回 输出{ value: ..., done: ... }
(不管左侧是啥)
2. 2.返回完成后,yield 得到一个返回值,该值为下一个next方法的参数,默认为undefined,假如左侧是赋值语句,如上。第一个next()方法执行值时,rest的值都为undefined,所以下方if不通过。
程序此时执行到右方yield处停止,左方赋值尚未执行。等到下一个next方法执行后,再得到返回值,执行左方赋值语句(划重点)。这是Generator函数的执行机制
3. 3.但假如此时next方法得到一个参数(n),该参数将作为刚刚暂停位置的yield的返回值。如上方代码,代码按照如下顺序:
第三次next执行后,rest被置为true,进入下方if语句,i被置为-1,进入第三次循环,i++,yield输出i此时为(0)
再来看一个例子来巩固Generator函数的执行机制
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 }
在这里再次强调yield的返回值和本身并无关系,他只是下一条next方法的参数。
如果你能够准确说出所有答案,那么就证明你能够较好的理解Generator的运行机制啦。
3.利用next方法注入参数
了解了以上知识,我们就可以利用next方法向Generator函数中注入参数,输出我们想要的结果。
但是从上面的例子看来,第一次执行next方法的参数是无效的,这怎么办呢,来再看一个例子:
function wrapper(generatorFunction) { return function (...args) { let generatorObject = generatorFunction(...args); generatorObject.next(); return generatorObject; }; } const wrapped = wrapper(function* () { console.log(`First input: ${yield}`); return 'DONE'; }); wrapped().next('hello!') // First input: hello
哈哈,就是利用高阶函数,偷偷的先执行一次next方法,再返回这个”用过的”的迭代器对象,好让我们认为自己是”第一次”执行next方法就得到的输出结果
2.利用for…of遍历
在前面我们介绍过,for…of 是一个神奇的方法,可以遍历各种数据结构(除了对象)。既然Generator函数的返回值是一个迭代器对象,那当然可以用for…of来对他进行遍历了。
function *foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let k of foo()) { console.log(k); } // 1, 2, 3, 4, 5 没有6
for…of 会在每个yield处进行返回。其实他是找到所有(done)为(false)的结果,并把(value)进行输出
来感受一下用Generator实现的斐波那契数列
function* fibonacci() { let [prev, curr] = [0, 1]; for (;;) { [prev, curr] = [curr, prev + curr]; yield curr; } } for (let n of fibonacci()) { if (n > 1000) break; console.log(n); }
3.Generator.prototype.throw()
Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
var g = function* () { try { yield; } catch (e) { console.log('内部捕获', e); } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('外部捕获', e); } // 内部捕获 a // 外部捕获 b
throw方法的参数直接传到Generator内部的catch中的e处
…在此先占一个空
4.Generator.prototype.return()
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 }
5.yield*
如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; foo(); yield 'y'; } for (let v of bar()){ console.log(v); } // "x" // "y"
上面代码中,foo和bar都是 Generator 函数,在bar里面调用foo,是不会有效果的。
这个就需要用到yield*
表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。
需要写成
function* bar() { yield 'x'; yield* foo(); yield 'y'; } for (let v of bar()){ console.log(v); } // 'x' // 'a' // 'b' // 'y'
yield*的本质其实就是对一个迭代器对象进行for…of遍历
function* concat(iter1, iter2) { yield* iter1; yield* iter2; } // 等同于 function* concat(iter1, iter2) { for (var value of iter1) { yield value; } for (var value of iter2) { yield value; } }
当目标函数中含有return时,yield*的返回值就为return的返回值。
6.Generator函数的this
Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。
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* g() { this.a = 11; } let obj = g(); obj.a // undefined
Generator函数中的this指向window
…占空
7.Generator是一个状态机
Generator 是实现状态机的最佳结构。比如,下面的clock函数就是一个状态机。
var ticking = true; var clock = function() { if (ticking) console.log('Tick!'); else console.log('Tock!'); ticking = !ticking; }
上面代码的clock函数一共有两种状态(Tick和Tock),每运行一次,就改变一次状态。这个函数如果用 Generator 实现,就是下面这样。
var clock = function* () { while (true) { console.log('Tick!'); yield; console.log('Tock!'); yield; } };
上面的 Generator 实现与 ES5 实现对比,可以看到少了用来保存状态的外部变量ticking,这样就更简洁,更安全(状态不会被非法篡改)、更符合函数式编程的思想,在写法上也更优雅。Generator 之所以可以不用外部变量保存状态,是因为它本身就包含了一个状态信息,即目前是否处于暂停态。
8.异步操作中的应用
Generator的一个重要的实际意义就是处理异步操作,改写回调函数
function* loadUI() { showLoadingScreen(); yield loadUIDataAsynchronously(); hideLoadingScreen(); } var loader = loadUI(); // 加载UI loader.next() // 卸载UI loader.next()
第一次执行loader.next(),显示loading遮罩,并加载数据,第二次执行loader.next(),隐藏loading遮罩
如上代码,将所有loading页面需要的逻辑都封装在一个函数中,省去了回环曲折的回调函数,逻辑非常清楚。
- es6 之 Generator(一)
- es6 之 Generator(二)
- ES6之生成器(Generator)
- 12、ES6 之Generator
- ES6之Generator
- es6 Generator (十五)
- 学习笔记:ES6之Generator
- es6新特性之generator
- Javascript的异步编程(下)及es6之generator
- ES6学习10(Generator)
- ES6--Generator
- ES6-generator
- 【es6】Generator
- ES6 Generator
- ES6 Generator
- 小试ES6:异步编程之Generator
- Generator 函数基础(一) (The Basics of ES6 Generators)
- es6 javascript 的Generator 函数 (上)
- SQL的一些操作
- XCTF嘉年华 re1 Writeup
- js内置方法的两种不同操作方式
- js java 运算符优先级用法总结
- js复制文字
- es6 之 Generator(一)
- spring boot thymeleaf 用法
- Sublime text 3安装时,add to explorer context menu是什么意思?
- D-S envidence theory(DS 证据理论)的基本概念和推理过程
- SQL注入
- SessionID的本质
- STM32-UCOS学习笔记1
- 更新换代----systemctl命令取代chkconfig和service
- Xcode重构功能怎么用我全告诉你