从零开始学_JavaScript_系列(53)——Generator函数(1)基本概念和示例
来源:互联网 发布:把别人淘宝制成csv 编辑:程序博客网 时间:2024/05/22 05:10
1、Generator基本概念
注:【遍历器】和【迭代器】是一个意思。
1.1、表象:
- 函数名有个星号(就是乘号*);
- 函数体内部使用yield表达式(yield翻译成中文:产生);
- 函数调用后返回值是一个遍历器对象,该对象可以调用next方法遍历yield表达式返回的值;
- 函数内部可有返回值,也可以没有(这个影响的是遍历器遍历到第一次done为true时,value的值是什么);
- 函数执行后并不直接执行函数内部代码,而是返回一个遍历器(参考上一章Iterator),但注意,他本身并没有
[Symbol.iterator]
属性
简单来理解,就是当函数名前面有星号时,调用函数的时候不会运行该函数,而是返回一个遍历器,该遍历器通过调用next方法可以遍历执行函数内部的语句,每次执行到yield表达式(或执行到函数结束)为止;
如示例代码:
function* foo() { // 星号在function关键词和函数名之间 yield 'first'; // yield表达式表示会被遍历到的内容 let second = 'sec'; // 函数内部可以有正常的js语句 yield second + 'ond'; // yield表达式只关心该表达式的返回值(即"second"字符串) return 'last' // 可以有return返回值也可以没有}foo[Symbol.iterator]; // undefined,说明没有这个属性let bar = foo();// bar:// {[[GeneratorStatus]]: "suspended"}// 原型链上有next、return、throw三个方法bar.next(); // {value: "first", done: false}bar.next(); // {value: "second", done: false}bar.next(); // {value: "last", done: true} 如果没有return,那么这里的value是undefinedbar; // {[[GeneratorStatus]]: "closed"} 出现done之后才会closed
1.2、运行:
函数本身的运行:
- Generator函数可以视为一个状态机;
- 简单来说,就是内部可能有A、B、C状态,初始是空状态,然后通过多次调用next方法,依次切换到A、B、C状态;
- 当之后没有其他状态时,调用next方法返回的对象的done属性的值为true;
这三句话可能比较抽象,可以结合下面代码的执行来理解:
代码的执行:
- 直接调用本函数,并不会执行函数内部的代码,而是返回一个遍历器;
- 此时什么都不执行,直到调用该遍历器的next方法为止;
- 第一次调用next时,从函数内部第一行开始执行,直到遇见yield表达式为止,输出yield表达式的值(即yield所在的那一段js语句的返回值);
- 第二次调用next时,从上一次yield表达式停止的地方开始继续执行,直到再次遇见yield表达式,或者到函数的结尾,或遇见return后停止;
- 注意,是执行到yield表达式为止,而不是yield所在的那一行代码,典型情况是:
console.log(yield "test");
其中的console.log是不执行的; - 每次执行next()后,返回值的是一个对象,有value属性和done属性,参考Iterator,其中value的值是yield表达式的值;
- 当结束到结尾时,或者遇见return时,done会变为true,在此之前,done为false;
- done变为true,没必要继续调用next了(就像Iterator接口那样);
首先,当然是上示例代码啦:
function* foo() { let a = 1; console.log('第一次执行') yield a; a++; console.log(yield "test"); a *= 2; return a}// 调用foo()let bar = foo();// 此时没有输出任何东西,bar的值// 第一次调用next(),下面的两行注释,其中第一行是console.log,第二行是返回值****bar.next();// 第一次执行// {value: 1, done: false}// 第二次调用next(),注意,这个时候没执行yield "test"外面的console.logbar.next();// {value: "test", done: false}// 第三次调用next(),这个时候执行了console.log(),但显然由于yield之前已经执行了,所以参数是空// 遇见return了,所以done变为true,value的值是return的返回值bar.next();// undefined// {value: 4, done: true}
看注释,就懂代码怎么执行的了。
另外提一句,每次通过foo()生成的遍历器,都是独立的,他们之间不会互相影响。
1.3、yield关键字
首先,yield在非Generator函数内不是关键字,只有在Generator函数内才是关键字,可以通过赋值,然后不会报错来证明。
因此,不要试图在非Generator函数内部的场合使用yield,肯定会报错的啦。
let yield = 1;console.log(yield); //1
1.4、yield表达式的值
yield表达式起到的是暂停函数执行的作用,该表达式的值是下一次调用next()时作为参数传入的值,默认情况下是undefined。
原因在于,每次执行到yield表达式时就会终止,等到下次执行的时候,相当于yield表达式所在的地方为空(上次执行过了,所以这次不会再执行一遍),所以值undefined。
那么怎么让yield表达式有值呢?答案是通过遍历器的next()方法的参数来传入。该参数将作为上一个yield表达式的值来使用;
function bar(val) { console.log(val, arguments.length)}function* foo() { console.log('Hello ' + (yield 123)); bar(yield 456)}let test = foo();test.next();// {value: 123, done: false}test.next();// Hello undefined// {value: 456, done: false}test.next('input');// input 1// {value: undefined, done: true}
另外提一句,由于第一次调用next()时,之前是没有yield表达式的,所以如果想在初始化的时候就输入值,需要进行特殊化处理,比如外面包一层,或者放弃第一次next输入参数,空调用一次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!// {value: "DONE", done: true}
// 空调用一次next()function* dataConsumer() { console.log(`1. ${yield}`); console.log(`2. ${yield}`); return 'result';}let genObj = dataConsumer();genObj.next();genObj.next('a')// 1. agenObj.next('b')// 2. b
最后,yield表达式如果单独使用,那么不需要括号,如果要将yield表达式与其他变量进行运算,那么需要使用圆括号将其括起来(如上面的示例);
1.5、Generator的简写
普通函数作为对象属性的时候可以简写,Generator函数作为对象属性的时候,虽然多了一个星号,但也可以简写,简写方法是将星号放在属性名前即可
如代码:
let foo = { * [Symbol.iterator]() { // some code }}
- 从零开始学_JavaScript_系列(53)——Generator函数(1)基本概念和示例
- 从零开始学_JavaScript_系列(54)——Generator函数(2)简单应用、throw和return
- 从零开始学_JavaScript_系列(62)——class(3)setter和getter、Generator、async函数
- 从零开始学_JavaScript_系列(64)——class的继承(1)基本概念、继承构造函数和class
- 从零开始学_JavaScript_系列(57)——Generator函数(5)状态机与函数的应用
- 从零开始学_JavaScript_系列(55)——Generator函数(3)yield*表达式
- 从零开始学_JavaScript_系列(56)——Generator函数(4)简写,this与继承
- 从零开始学_JavaScript_系列(60)——class(1)基本概念
- 从零开始学_JavaScript_系列(58)——Thunk函数
- 从零开始学_JavaScript_系列(59)——async函数
- 从零开始学_JavaScript_系列(30)——NodeList
- 从零开始学_JavaScript_系列(32)——事件广播
- 从零开始学_JavaScript_系列(43)——Symbol简述
- 从零开始学_JavaScript_系列(47)——Reflect
- 从零开始学_JavaScript_系列(29)——apply和call
- 从零开始学_JavaScript_系列(44)——ES6新增数据结构:Set类型和WeakSet
- 从零开始学_JavaScript_系列(45)——ES6新增数据结构:Map和WeakMap
- 从零开始学_JavaScript_系列(51)——Promise(4)Promise.resolve和Promise.reject
- [Leetcode] 113, 53, 124
- 解决git以 https和ssh方式 每次都要输入用户名和密码问题
- 缓存算法(页面置换算法)-FIFO、LFU、LRU
- GetAsyncKeyState函数
- 540. Single Element in a Sorted Array
- 从零开始学_JavaScript_系列(53)——Generator函数(1)基本概念和示例
- 机器学习十大常用算法小结
- 使用数组制作简易的用户管理系统【java】
- 使用CSS将图片转换成黑白(灰色、置灰)
- RecycleView初识
- 2017年春招“森林举行运动会”编程题
- 秦风测试
- Myeclipse为Java Project配置JRE(或者JDK)环境
- java打印一个菱形