We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
迭代器和生成器在前端业务里经常有用到,但是可能感受不太明显。特别是生成器,在react中如果你有用过redux中间健redux-saga那么你一定对生成器很熟悉。
react
redux
redux-saga
本文是笔者对于迭代器与生成器的理解,希望在项目中有所帮助.
在阅读本文之前,主要会从以下几点去探讨迭代器/生成器
迭代器是什么,想想为什么会有迭代器
生成器又是什么,它解决了什么样的问题
以实际例子阐述迭代器与生成器
正文开始...
参考mdn上解释,迭代器是一个对象,每次调用next方法返回一个{done: false, value: ''},每次调用next返回当前值,直至最后一次调用时返回{value:undefined,done: true}时结束,无论后面调用next方法都只会返回{value: undefined,done:true}
mdn
next
{done: false, value: ''}
{value:undefined,done: true}
{value: undefined,done:true}
在过往的业务中,你一定用过for ... of循环过数组或者Map
for ... of
数组
Map
const arr = [ { name: 'Maic', age: 18 }, { name: 'Tom', age: 10 } ] for (let item of arr) { console.log(item); /* {name: 'Maic', age:18},{name: 'Maic', age:18} */ }
因为数组就是可以支持迭代器对象,并且for...of可以中断循环,关于循环中断可以参考以前写的一篇文章你不知道的JS循环中断
for...of
因为数组是支持可迭代的对象,如果使用迭代器获取每组数据应该怎么做呢?
const arr = [ { name: 'Maic', age: 18 }, { name: 'Tom', age: 10 } ] const iterator = arr[Symbol.iterator](); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
我们执行node index.js可以看到运行结果 当我们每次调用iterator.next()值时,都会返回当前的值,并且返回的值是{value: xxx, done: false},直至最后,返回的值{value: undefined, done: true }
node index.js
iterator.next()
{value: xxx, done: false}
{value: undefined, done: true }
不知道你发现没有,上面迭代器,我是通过数组访问Symbol.iterator方法,再调用返回的next方法,最后得到当前的值
Symbol.iterator
我们可以在控制台看下 数组是有这个Symbol.iterator属性的
从以上迭代器特征中,我们可以得知,数组是通过一个Symbol.iterator方法,返回一个next方法,并且next方法返回{value: xx, done: false},我们模拟一个迭代器
{value: xx, done: false}
const iteratorObj = { value: [1, 2, 3], count: -1, next() { this.count++ return { value: this.value[this.count], done: !this.value[this.count] } } } console.log(iteratorObj.next()); console.log(iteratorObj.next()); console.log(iteratorObj.next()); console.log(iteratorObj.next());
打印的结果依次是:
{ value: 1, done: false } { value: 2, done: false } { value: 3, done: false } { value: undefined, done: true }
此时你会发现iteratorObj就基本实现了一个迭代器的基本功能。
iteratorObj
我们用一个对象模拟了迭代器,但是我们发现这个对象迭代器貌似没法复用
因此我们创建一个迭代器的工具函数
function createIteror(arr = []) { let count = -1; return { next: function () { count++ return { value: arr[count], done: count >= arr.length } } } } const newCreateInteror = createIteror([1, 2, 3]); console.log(newCreateInteror.next()); console.log(newCreateInteror.next()); console.log(newCreateInteror.next()); console.log(newCreateInteror.next());
结果是:
{ value: 1, done: false } { value: 2, done: false } { value: 3, done: false } { value: undefined, done: false }
因此createIteror这个方法就具备了迭代器的功能
createIteror
我们在这之前用iteratorObj模拟了一个具备迭代器的功能,但是如何让真正的对象支持迭代呢
迭代器
不知道你发现没有,其实数组原型上是有Symbol.iterator,所以如果要让一个对象支持迭代器功能,那么只需要遵循迭代协议即可
迭代协议
const coustomerInteror = { value: [1, 2, 3], // 让对象支持迭代器协议,需要增加一个Symbol.iterator可访问的方法,并返回一个迭代器对象,迭代器对象可以调用`next`方法,在next方法中返回一个当前对象的值 [Symbol.iterator]: function () { let count = -1; return { next: () => { count++; return { value: this.value[count], done: count >= this.value.length } } } } } const newInter = coustomerInteror[Symbol.iterator](); console.log(newInter.next()); console.log(newInter.next()); console.log(newInter.next()); console.log(newInter.next()); for (let item of coustomerInteror) { console.log(item, '=result') }
可以看到打印的结果
因此让一个对象支持迭代器功能,只需要新增一个Symbol.iterator方法,遵循迭代器原则
我们从以上结果得知要想一个对象支持迭代器功能,必须要有Symbol.iterator这样的迭代器协议
迭代器协议
因此我们可以在Object原型上新增这样的一个迭代器协议
Object
// 在Object.prototype原型上扩展Symbol.iterator Object.prototype[Symbol.iterator] = function () { let count = -1; return { next: () => { count++; const keys = Object.keys(this); return { value: this[keys[count]], done: count >= keys.length } } } } const cobj = { a: 1, b: 2 }; const iteror = cobj[Symbol.iterator](); console.log(iteror.next()); console.log(iteror.next()) console.log(iteror.next()) for (let item of cobj) { console.log(item, '=rs') } const [a, b] = cobj; console.log(a,b);
执行的结果是:
{ value: 1, done: false } { value: 2, done: false } { value: undefined, done: true } 1 =rs 2 =rs 1 2
你会发现当我们使用数组解构时,居然可以解构对象的值
const [a, b] = cobj; console.log(a,b);
本质上就是我们迭代器会自动调用iteror.next().value然后一一赋值返回了。
iteror.next().value
所以支持迭代器对象不仅可以for...of也可以被数组解构,这样所有var obj = {}这样类似申明的对象都可以支持迭代器了。
数组解构
var obj = {}
我们现在有个需求,需要支持通过构造函数new出来的对象支持可迭代器功能
new
具体我们看下代码
class Person { constructor() { this.name = 'Maic'; this.age = 18; } [Symbol.iterator]() { let count = -1; return { next: () => { count++; // 获取对象的所有属性key const keys = Object.keys(this); return { value: this[keys[count]], done: count >= keys.length } } } } } const person = new Person(); const iter = person[Symbol.iterator](); console.log(iter.next(), '=='); console.log(iter.next(), '=='); console.log(iter.next(), '=='); for (let item of person) { if (item === 'Maic') { break; // 可以中断循环 } console.log(item) // 这里并不会打印 } const [name, age] = person; console.log(name, age)
本质上也是在构造函数Person内部新增了Symbol.iterator方法,并且返回了一个迭代器对象
Person
打印的结果如下:
{ value: 'Maic', done: false } == { value: 18, done: false } == { value: undefined, done: true } == Maic 18
至此你应该非常了解迭代器的对象的特性了哈
能够for...of循环中断,且能够数组解构、扩展,所以你知道为啥会有迭代器了吗?
扩展
首先是数组Array,Map,Set
Array
Set
只要是有迭代器特性,那么就可以被for...of,数组解构等
这是es6新增的,参考generator解释,生成器是一种异步解决的方案,也可以理解一种函数内部的状态机,能中断函数,也就是说能够控制函数的运行
具体我们以一个实际例子看下生成器是什么
function* genter() { yield 1; yield 2; } const gen = genter(); console.log(gen);
我们定义了一个普通函数,但是这个普通函数比较特殊,前面有*,这就是定义生成器函数,我们暂定把gen这个称呼为生成器对象
*
gen
然后我们打印生成器对象,实际是就像函数调用一样,不过此时返回的是一个Object Generator
Object Generator
Object [Generator] {}
但是我们继续看下
function* genter() { yield 1; yield 2; } const gen = genter(); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); for (let item of gen) { console.log(item); }
此时打印的结果是
{ value: 1, done: false } { value: 2, done: false } { value: undefined, done: true } 1 2
我们看下生成器函数内部是有yield这样的东西
yield
实际上这就是内部函数的状态机,当你使用用生成器时,你调用next就会返回一个对象,并且像迭代器一样返回{value: xxx, done: false} 因此在使用上,我们必须认清,生成器必须是带有*定义的函数,内部是yield执行的状态机
状态机
当我们调用函数生成器时,并不会立即执行,返回一个遍历对象并返回一个next方法,当遍历对象调用next时,就会返回yield执行的状态机,并返回一个迭代器对象的值,yield会在当前状态暂停,只有调用next时,就会执行yield,yield
value表示当前yield的值,done:false表示当前遍厉没有结束,如果继续执行gen.next()那么就会返回当前的yield值,直到done:true时,表示当前遍历对象已经完全遍历完毕。
value
done:false
gen.next()
done:true
我们再来看下这段代码
function* start() { console.log('start') } const genterStart = start();
此时你会发现并不会调用start方法
start
但是你执行下面代码时,才会立即调用
function* start() { console.log('start') } const genterStart = start(); setTimeout(() => { genterStart.next(); }, 1000)
我们会发现定时定时1s妙后才执行start方法,而且是通过next去执行的。
所以此时这个start变成了一个暂缓的执行函数,同时我们要注意yield只能用在*定义的生成器内部
暂缓的执行函数
我们在以往的业务中多少有写过扁平化数组,通常也是用递归实现多维数组的打平,现在我们尝试用生成器来实现一个扁平化数组
function* flat(arr) { for (let i = 0; i < arr.length; i++) { const item = arr[i]; if (Array.isArray(item)) { // 如果是数组,则递归 yield* flat(item) } else { yield item } } } const sourceArr = [1, [[2, 3], 4], [5, 6]] const result = []; for (let item of flat(sourceArr)) { result.push(item) } console.log(result)// [1,2,3,4,5,6]
但是这个flat貌似不太通用,因此可不可以像原生flat方法一样,因此我们向下面这样做,在Array的原型上新增一个方法,让所有的数组都能访问这个自定义方法
flat
// Array的prototype中绑定一个公有方法 Array.prototype.$myFlat = function () { // 定义一个flat生成器 function* flat(arr) { for (let i = 0; i < arr.length; i++) { const item = arr[i]; if (Array.isArray(item)) { // 递归当前flat yield* flat(item); } else { yield item } } } const ngen = flat(this); return [...ngen]; } const sourceArr2 = [1, 2, [3, 4, 5, 6, [7, 8]]] console.log(sourceArr2.$myFlat())
因此$myFlat这个方法就像原生flat一样了
$myFlat
当我们看到用*定义的方法,就变成一个生成器,此时我们调用这个生成器方法,那么此时就可以for...of循环了
function* test() { yield 1; yield 2; yield 3; } const gtest = test(); // gtest.next() { value:1,done: false} // for (let item of gtest) { // console.log(item) 这里相当于已经调用了gtest.next().value // } const [a, b, c, d] = gtest; console.log('abc', a, b, c, d)
打印的结果就是:
abc 1 2 3 undefined
我们进一步测试一下:
function* test() { yield 1; yield 2; yield 3; } const gtest = test(); console.log(gtest[Symbol.iterator]() === gtest) // true
这里我们就会发现gtest可以通过Symbol.iterator这个方法直接调用,居然于它本身相等。
gtest
从控制台中我们可以知道gtest返回就是一个生成器对象,它的构造函数是GeneratorFunction,并且原型上有Symbol.iterator,而且是一个迭代器。
GeneratorFunction
当你使用
... gtest[Symbol.iterator]().next(); gtest[Symbol.iterator]().next() gtest[Symbol.iterator]().next() // 以上等价于 /* gtest.next(); gtest.next(); gtest.next(); */
可以看下控制台打印的结果就知道了
所以大概了解生成器与迭代器的关系了么?本质上是通过生成器对象的prototype的Symbol.iterator连接了起来
当我们在生成器函数内部return时,那么当调用next迭代完所有的值时,继续调用next,则会返回return的值
return
什么意思,我们看下下面这段代码
function* test() { yield 1; yield 2; yield 3; return 4; } const gtest = test(); console.log(gtest.next());// {value: 1,done: false} console.log(gtest.next()); // {value: 2,done: false} console.log(gtest.next()); // {value: 3,done: false} console.log(gtest.next()); // {value: 4,done: true} console.log(gtest.next()); // {value: undefined,done: true}
yield后面可以是变量或者具体函数返回值 你可以这么写
function* test() { let b = 2; const logNum = (num) => num yield 1; yield b; yield logNum(5); return 4; } const gtest = test(); console.log(gtest.next()); console.log(gtest.next()); console.log(gtest.next()); console.log(gtest.next());
执行结果如下
{ value: 1, done: false } { value: 2, done: false } { value: 5, done: false } { value: 4, done: true } { value: undefined, done: true }
生成器传参数
function* test() { let b = 2; const logNum = num => num const n = yield 1; // n为下面第二个yield(10)这里n = 10 yield n * b; // 这个n就是第二个next传入的,会把第一个yield当返回值,传给下个yield yield logNum(5); return 4; } const gtest = test(); console.log(gtest.next()); console.log(gtest.next(10)); // 20 console.log(gtest.next()); console.log(gtest.next()); /* { value: 1, done: false } { value: 20, done: false } { value: 5, done: false } { value: 4, done: true } { value: undefined, done: true } */
主要是在yield捕获异常,具体看下下面这个简单的例子
function* test() { try { yield 1; } catch (error) { console.log(error) } try { yield 2; } catch (error) { console.log(error, '---2'); } } const gen = test(); console.log(gen.next()) gen.throw('错误了'); console.log(gen.next()) // 并不会运行
当我们执行gen.next()时会执行yield 1此时返回{value: 1, done: false} 当我们执行gen.throw时,此时yield 2会暂停,并且就会中断了。并且后面的gen.next()就是默认返回{value: undefined, done: true}
yield 1
{value: 1, done: false}
gen.throw
yield 2
{value: undefined, done: true}
我们在这之前都见过yield只能在生成器中使用,那到底有哪些使用,我们写个例子熟悉一下
function* a() { yield 1; yield 2; } function* b() { yield* a(); yield 3; } const bGen = b(); // console.log([...bGen]); [1,2,3] // console.log(bGen.next()) 注意这个与上面不能同时使用,不然这个bGen就是返回{value: undefined, done: true}
yield后面能是函数返回值,能是变量,也可以是一个生成器函数
const obj = { * getName() { yield 'Maic' } } const person = obj.getName(); console.log(person.next()); // {value: 'Maic', done: false}
等价
const obj = { getAge: function *() { yield 18 } } const age = obj.getAge(); console.log(age.next()); // {value: 18, done: false}
function* a() { yield 1; yield 2; } // new a() error
在以往业务中肯定有这种场景,点击页面首先加载loading,然后请求数据,当数据请求成功后,就结束loading,我们看一段简单的伪代码
loading
// 定义了一个获取数据的生成器方法,setTimeout模拟异步请求 function* getList() { yield new Promise((resolve, reject) => { setTimeout(() => { resolve({ code: 0, data: [ { name: 'Maic', age: 18 }, { name: 'Web技术学苑', age: 20 } ] }) }, 1000) }) }
然后我们定义一个loadUI生成器
loadUI
function* loadUI() { console.log('正在加载中...,开启loading...'); yield* getList(); console.log('加载完成,关闭loading') } const loadStart = loadUI(); // 加载数据,调用next().value 获取yield的值 const currentData = loadStart.next().value; currentData.then((res) => { if (res) { console.log(res); } loadStart.next(); // 关闭加载,加载完成,执行yield 后面的代码 });
或者你可以这样
function* loadUI() { console.log('正在加载中...,开启loading...'); const { data } = yield getList(); console.log(data); console.log('加载完成,关闭loading') } const loadStart = loadUI(); function getList() { const mockData = { code: 0, data: [ { name: 'Maic', age: 18 }, { name: 'Web技术学苑', age: 20 } ] }; setTimeout(() => { loadStart.next(mockData);// next传入数据当成yield状态机的返回值 }, 1000) } // 继续执行yield后面的代码 loadStart.next();
运行的结果依旧是一样的,这样我就可以通过loadStart精准的控制数据请求在哪里执行了。如果我最后一行代码不执行,那么久不会执行后面的打印代码了,从而达到精准的控制函数内部的执行。
loadStart
假设有一个场景,就是fn2依赖fn1的结果而决定是否是否执行,fn3依赖fn2的状态是否继续执行,那怎么设计呢?生成器可以帮我们解决这个需求问题
fn2
fn1
fn3
function fn1() { return { code: 1, message: '我是fn1,你成功了,请进行下一步' } } function fn2() { return { code: 0, message: '我是fn2,失败了' } } function fn3() { console.log('恭喜你,闯关成功了...'); } const source = [fn1, fn2, fn3]; function* main(arr = []) { for (let i = 0; i < arr.length; i++) { yield arr[i]( "i") } } const it = main(source); for (let item of it) { console.log(item) if (item.code === 0) { break; } }
{ code: 1, message: '我是fn1,你成功了,请进行下一步' } { code: 0, message: '我是fn2,失败了' }
当fn2返回code:0就会终止break中止,当fn2中返回的code是1时,才会进入下一个迭代
code:0
break
code
当我们for...of时,内部会依次调用next方法进行遍历数据。因为是迭代器,每次next的值返回的就是yield的值,并且返回{value: xxx, done: false},直到最后{value: undefined, done: true}
迭代器是一个对象,迭代器对象有一个next方法,当我们调用next方法时,会返回一个对象{value: xx, done: false},value就是当前迭代器迭代的具体值,当迭代器对象每调用一次next方法时,就会获取当前的值,直到迭代完全,最后返回{done: true, value: undefined}
{done: true, value: undefined}
每一个迭代器都可以被for...of、数组解构以及数组扩展
生成器函数,yield可以中断函数,当我们调用函数生成器时,实际上并不会立即执行生成器函数,这个调用的函数生成器在调用时会返回一个迭代器,每次调用next方法会返回一个对象,这个对象的值跟迭代器一样,并且返回的value是yield的值,每次调用,才会执行yield,后面的代码会中断。只有继续调用next才会继续往后执行。
生成器函数调用返回的事一个迭代器,具备迭代器所有特性,yield这个状态机只能在生成器函数内部使用
以实际例子对对象扩展支持迭代器特性,如果需要支持迭代器特征,那么必须原型上扩展Symbol.iterator方法,以$flat在数组原型上利用函数生成器实现扁平化数组等。
对象
$flat
本文code-example
The text was updated successfully, but these errors were encountered:
No branches or pull requests
迭代器和生成器在前端业务里经常有用到,但是可能感受不太明显。特别是生成器,在
react
中如果你有用过redux
中间健redux-saga
那么你一定对生成器很熟悉。本文是笔者对于迭代器与生成器的理解,希望在项目中有所帮助.
在阅读本文之前,主要会从以下几点去探讨迭代器/生成器
迭代器是什么,想想为什么会有迭代器
生成器又是什么,它解决了什么样的问题
以实际例子阐述迭代器与生成器
正文开始...
迭代器是什么
参考
mdn
上解释,迭代器是一个对象,每次调用next
方法返回一个{done: false, value: ''}
,每次调用next
返回当前值,直至最后一次调用时返回{value:undefined,done: true}
时结束,无论后面调用next
方法都只会返回{value: undefined,done:true}
在过往的业务中,你一定用过
for ... of
循环过数组
或者Map
因为数组就是可以支持迭代器对象,并且
for...of
可以中断循环,关于循环中断可以参考以前写的一篇文章你不知道的JS循环中断因为数组是支持可迭代的对象,如果使用迭代器获取每组数据应该怎么做呢?
我们执行
node index.js
可以看到运行结果当我们每次调用
iterator.next()
值时,都会返回当前的值,并且返回的值是{value: xxx, done: false}
,直至最后,返回的值{value: undefined, done: true }
不知道你发现没有,上面迭代器,我是通过数组访问
Symbol.iterator
方法,再调用返回的next
方法,最后得到当前的值我们可以在控制台看下
数组是有这个
Symbol.iterator
属性的从以上迭代器特征中,我们可以得知,数组是通过一个
Symbol.iterator
方法,返回一个next
方法,并且next
方法返回{value: xx, done: false}
,我们模拟一个迭代器模拟迭代器
打印的结果依次是:
此时你会发现
iteratorObj
就基本实现了一个迭代器的基本功能。我们用一个对象模拟了迭代器,但是我们发现这个对象迭代器貌似没法复用
因此我们创建一个迭代器的工具函数
结果是:
因此
createIteror
这个方法就具备了迭代器的功能我们在这之前用
iteratorObj
模拟了一个具备迭代器
的功能,但是如何让真正的对象支持迭代呢让对象支持迭代器功能
不知道你发现没有,其实数组原型上是有
Symbol.iterator
,所以如果要让一个对象支持迭代器功能,那么只需要遵循迭代协议
即可可以看到打印的结果
因此让一个对象支持迭代器功能,只需要新增一个
Symbol.iterator
方法,遵循迭代器
原则支持所有对象可迭代
我们从以上结果得知要想一个对象支持迭代器功能,必须要有
Symbol.iterator
这样的迭代器协议
因此我们可以在
Object
原型上新增这样的一个迭代器协议
执行的结果是:
你会发现当我们使用数组解构时,居然可以解构对象的值
本质上就是我们迭代器会自动调用
iteror.next().value
然后一一赋值返回了。所以支持
迭代器
对象不仅可以for...of
也可以被数组解构
,这样所有var obj = {}
这样类似申明的对象都可以支持迭代器了。构造函数支持可迭代
我们现在有个需求,需要支持通过构造函数
new
出来的对象支持可迭代器功能具体我们看下代码
本质上也是在构造函数
Person
内部新增了Symbol.iterator
方法,并且返回了一个迭代器对象打印的结果如下:
至此你应该非常了解迭代器的对象的特性了哈
能够
for...of
循环中断,且能够数组解构
、扩展
,所以你知道为啥会有迭代器了吗?那些原生API支持迭代器
首先是数组
Array
,Map
,Set
只要是有迭代器特性,那么就可以被
for...of
,数组解构等生成器
这是es6新增的,参考generator解释,生成器是一种异步解决的方案,也可以理解一种函数内部的状态机,能中断函数,也就是说能够控制函数的运行
具体我们以一个实际例子看下生成器是什么
我们定义了一个普通函数,但是这个普通函数比较特殊,前面有
*
,这就是定义生成器函数,我们暂定把gen
这个称呼为生成器对象然后我们打印生成器对象,实际是就像函数调用一样,不过此时返回的是一个
Object Generator
但是我们继续看下
此时打印的结果是
我们看下生成器函数内部是有
yield
这样的东西实际上这就是内部函数的
状态机
,当你使用用生成器时,你调用next
就会返回一个对象,并且像迭代器一样返回{value: xxx, done: false}
因此在使用上,我们必须认清,生成器必须是带有
*
定义的函数,内部是yield
执行的状态机当我们调用函数生成器时,并不会立即执行,返回一个遍历对象并返回一个
next
方法,当遍历对象调用next
时,就会返回yield
执行的状态机,并返回一个迭代器对象的值,yield
会在当前状态暂停,只有调用next
时,就会执行yield
,yield
value
表示当前yield
的值,done:false
表示当前遍厉没有结束,如果继续执行gen.next()
那么就会返回当前的yield
值,直到done:true
时,表示当前遍历对象已经完全遍历完毕。我们再来看下这段代码
此时你会发现并不会调用
start
方法但是你执行下面代码时,才会立即调用
我们会发现定时定时1s妙后才执行
start
方法,而且是通过next
去执行的。所以此时这个
start
变成了一个暂缓的执行函数
,同时我们要注意yield
只能用在*
定义的生成器内部生成器-扁平化数组
我们在以往的业务中多少有写过扁平化数组,通常也是用递归实现多维数组的打平,现在我们尝试用生成器来实现一个扁平化数组
但是这个
flat
貌似不太通用,因此可不可以像原生flat
方法一样,因此我们向下面这样做,在Array
的原型上新增一个方法,让所有的数组都能访问这个自定义方法因此
$myFlat
这个方法就像原生flat
一样了生成器与迭代器的关系
当我们看到用
*
定义的方法,就变成一个生成器,此时我们调用这个生成器方法,那么此时就可以for...of
循环了打印的结果就是:
我们进一步测试一下:
这里我们就会发现
gtest
可以通过Symbol.iterator
这个方法直接调用,居然于它本身相等。从控制台中我们可以知道
gtest
返回就是一个生成器对象,它的构造函数是GeneratorFunction
,并且原型上有Symbol.iterator
,而且是一个迭代器。当你使用
可以看下控制台打印的结果就知道了
所以大概了解生成器与迭代器的关系了么?本质上是通过生成器对象的prototype的
Symbol.iterator
连接了起来生成器函数的return
当我们在生成器函数内部return时,那么当调用
next
迭代完所有的值时,继续调用next
,则会返回return
的值什么意思,我们看下下面这段代码
yield
后面可以是变量或者具体函数返回值你可以这么写
执行结果如下
生成器传参数
生成器捕获异常
主要是在
yield
捕获异常,具体看下下面这个简单的例子当我们执行
gen.next()
时会执行yield 1
此时返回{value: 1, done: false}
当我们执行
gen.throw
时,此时yield 2
会暂停,并且就会中断了。并且后面的gen.next()
就是默认返回{value: undefined, done: true}
yield状态机
我们在这之前都见过
yield
只能在生成器中使用,那到底有哪些使用,我们写个例子熟悉一下yield
后面能是函数返回值,能是变量,也可以是一个生成器函数让一个对象的方法支持生成器
等价
生成器不能为new
生成器异步操作
在以往业务中肯定有这种场景,点击页面首先加载
loading
,然后请求数据,当数据请求成功后,就结束loading
,我们看一段简单的伪代码然后我们定义一个
loadUI
生成器或者你可以这样
运行的结果依旧是一样的,这样我就可以通过
loadStart
精准的控制数据请求在哪里执行了。如果我最后一行代码不执行,那么久不会执行后面的打印代码了,从而达到精准的控制函数内部的执行。控制多个函数按顺序执行
假设有一个场景,就是
fn2
依赖fn1
的结果而决定是否是否执行,fn3
依赖fn2
的状态是否继续执行,那怎么设计呢?生成器可以帮我们解决这个需求问题结果是:
当
fn2
返回code:0
就会终止break
中止,当fn2
中返回的code
是1时,才会进入下一个迭代当我们
for...of
时,内部会依次调用next
方法进行遍历数据。因为是迭代器,每次next
的值返回的就是yield
的值,并且返回{value: xxx, done: false}
,直到最后{value: undefined, done: true}
总结
迭代器是一个对象,迭代器对象有一个
next
方法,当我们调用next
方法时,会返回一个对象{value: xx, done: false}
,value
就是当前迭代器迭代的具体值,当迭代器对象每调用一次next
方法时,就会获取当前的值,直到迭代完全,最后返回{done: true, value: undefined}
每一个迭代器都可以被
for...of
、数组解构
以及数组扩展生成器函数,
yield
可以中断函数,当我们调用函数生成器时,实际上并不会立即执行生成器函数,这个调用的函数生成器在调用时会返回一个迭代器,每次调用next
方法会返回一个对象,这个对象的值跟迭代器一样,并且返回的value
是yield
的值,每次调用,才会执行yield,后面的代码会中断。只有继续调用next
才会继续往后执行。生成器函数调用返回的事一个迭代器,具备迭代器所有特性,
yield
这个状态机只能在生成器函数内部使用以实际例子对
对象
扩展支持迭代器特性,如果需要支持迭代器特征,那么必须原型上扩展Symbol.iterator
方法,以$flat
在数组原型上利用函数生成器实现扁平化数组等。本文code-example
The text was updated successfully, but these errors were encountered: