侧边栏壁纸
  • 累计撰写 8 篇文章
  • 累计创建 5 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Generator

郝帅德沃
2023-12-18 / 0 评论 / 0 点赞 / 6 阅读 / 31249 字

Generator函数

执行Generator函数会返回一个遍历器对象。返回的遍历器对象可以依次遍历Generator函数内部的每一个状态

unction* generator(){
  console.log('0000')
  yield '第一次'
  const num = 2+3
  console.log(num)
  yield console.log('第二次')
  console.log('第三次')
}
const test = generator()
// console.log(test.next())
// test.next()
// test.next()
// test.next()
let sum = 0
for(let value of test){
  console.log(value+'11')
  sum = sum+1
}
console.log(sum)
// 0000
// 第一次11
// 5
// 第二次
// undefined11
// 第三次
// 2

调用了generator函数之后,该函数不会马上执行,返回的也不是函数的运行结果。而是返回一个指向内部的指针对象。也就是遍历器对象,可以执行next方法

所以其中test不可以直接执行,需要调用遍历器对象的next()方法,使指针移向下一个状态。每次调用next方法,内部指针就从函数头部或者上一次停下的地方开始执行,直到遇到下一条yield语句(或者return)语句

总结:Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法则可以恢复执行

返回的对象代表了这个函数的内部指针

调用next跟遍历器一样会返回value跟done两个数据。value是yield后面那个表达式的值。done表示是否便利完成

yield表达式

Generator函数本质是可以暂停执行的函数。yield语句就是暂停的标志

如果在执行时没有碰到新的yield语句,则会一直运行到函数结束。直到语句return为止,然后将return语句后面的表达式作为返回对象value的值

如果没有return.则返回的value为undefined

return语句不具备记忆功能.一个函数里面只能执行一次

假如不用yield语句的话,Generator函数就变成了一个暂缓执行的函数

const gen = function(){
  console.log('执行了')
}
const gen2 = gen()
//在赋值的时候就会马上执行
const gen = function*(){
  console.log('执行了')
}
const gen2 = gen()
gen2.next()
//需要在调用next的时候才会执行

yiled语句只能用在Generator函数里面

  console.log('第一次'+(yield '第一次'))//也可以这样使用,在语句里面的话,需要加括号

可以把Generator赋值给对象的Symbol.interator属性,使得该对象具有Iterator接口

let obj = {}
const generator = function*(){
  yield 1
  yield 2
  yield 3
}
// const gen2 = generator().next()
obj[Symbol.iterator] = generator
// console.log(obj[Symbol.iterator])
// for(let value of obj){
//   console.log(value)
// }
const arr = [...obj]
console.log(arr)
//输出 [1,2,3]

其中Symbol属性是一个遍历器对象生成函数,执行后返回它自己

Generator函数执行后返回的遍历器对象,该对象也有Symbol.iterator属性。执行后返回自己

next方法的参数

yield语句本身没有返回值,next方法可以带有一个参数,该参数会被当作上一条yield语句的返回值。

Generator函数从暂停状态到恢复运行,其上下文状态是不变的,通过next方法的参数就可以在Generator函数开始运行后继续向函数内部注入值

function* f(){
  for(let i = 0;true;i++){
    let reset = yield i;
    reset&&(console.log(i))
    reset && (i = -1);
  }
}
const g = f()
console.log(g.next())
console.log(g.next())
console.log(g.next(true))
// { value: 0, done: false }
// { value: 1, done: false }
// 1
// { value: 0, done: false }
//在执行第三条语句时,reset先接收到了nex函数的参数变成了true,然后i变为-1,执行+1操作后返回
let test = function*(){
  let b = 0
  console.log('test')
  let a = yield console.log('a')
  console.log(a)//6 yield表达式为6
  yield console.log(b)//0 原来的值不变,next传入的参数是yield表达式的值
}
let f = test()
f.next()
//第一个next会输出a
f.next(6)
function * f(x){
  const y = 2*(yield (x+1))
  //返回x+1
  const z = yield(y/3)
  //y的值是yield的返回值,且下一个next函数没有传参数,所以值为undefind
  return (x+y+z)
}
const g = f(5)
console.log(g.next())
console.log(g.next(6))
console.log(g.next(5))
/*
1.传入参数x,
{ value: 6, done: false }
然后获取下个next的参数,6,此时y=2*6 = 12
{ value: 4, done: false }
获取next参数,5此时,z=next传入的参数,5
返回值为12+5+5 22
{ value: 6, done: false }
{ value: 4, done: false }
{ value: 22, done: true }
*/

第一次使用next方法传入参数是无效的,V8直接忽略第一次的参数

完成fibonacci数列

function* fibonacci(){
  let [prev,curr] = [0,1]
  for(;;){
    [prev,curr] = [curr,curr+prev]
    yield curr
  }
}
for(let n of fibonacci()){
  if(n>1000) break
  console.log(n)
}

fibonacci实现

function fibonacci(number){
  let[prev,curr] = [0,1]
  for(let i = 0;i<number;i++){
    [prev,curr] = [curr,prev+curr]
  }
  return number === 0 ? 0:curr
}

让对象变得可遍历

给对象加上可遍历接口

let obj = {name:'zd',age:18}
let propkeys = Reflect.ownKeys(obj)
for(let n of propkeys){
  console.log(obj[n])
}

一旦遍历接口返回的done为true,遍历就会终止,此时对应的value不会返回

拓展运算符,解构赋值和Array。from方法内部调用的都是遍历接口,可以将Generator对象作为参数

function* f(){
  yield 1;
  yield 2;
  yield 3;
  return 4;
}
console.log([...f()])
// [ 1, 2, 3 ]
console.log(Array.from(f()))
// [ 1, 2, 3 ]
let [x,y,z] = f()
console.log([x,y,z])
// [ 1, 2, 3 ]
console.log(f().next())
//此时创建了一个新的指针对象,两个都是指向的第一个地址
console.log(f().next())

遍历器对象的throw方法

Generator函数返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,然后在generator函数体内进行捕获。

generator内部的catch语句只会在执行第一次的时候捕获到错误,第二次抛出的错误是不会捕获到的。

这个和全局的throw方法不一样,全局的throw抛出的异常只能由函数体外的catch捕获

如果generator函数内部没有部署try...catch代码块,那么遍历器对象抛出的错误会被外部的try...catch捕获。

如果generator函数内部部署了try...catch代码块,遍历器对象抛出的异常不影响下一次遍历,否者遍历将中止。

const f = function*(){
  try {
    yield console.log('hello')
    yield console.log('world')
  } catch (e) {
    console.log(e)
  }
  yield console.log('after error')
}
const g = f()
g.next()
g.throw('e')
//hello
//e
//after error

throw方法被捕获后会附带执行下一条yield表达式

一旦generator在执行的过程中抛出错误。就不会再执行下去了。如果再调用next方法会返回一个值为undefined,done为true的对象。即js引擎认为这个generator已经结束运行了。

return 方法

遍历器对象有一个return方法,返回给定的值,并结束当前generator函数的遍历

let delegatedIterator = function*(){
  yield 'hello';
  yield 'bye'
}
let delegatingIterator = function*(){
  try {
    yield 'generator'
  yield* delegatedIterator()
  yield 'finally'
  } finally {
    console.log('finally里面的代码')
  }
}
let f2 = delegatingIterator()
console.log(f2.next())
console.log(f2.next())
console.log(f2.return('truefinally'))
// { value: 'generator', done: false }
// { value: 'hello', done: false }
// { value: 'truefinally', done: true }

如果generator函数里面有try...finally的代码块,则在执行完return前会先执行完finally里面的代码

yield*表达式

在一个generator函数里面执行另外一个generator函数,需要使用yield*语句。

从语法的角度上来说,直接跟在yield表达式后面的generator函数返回的是一个遍历器对象。而使用yield语句之后,表明返回的是一个遍历器对象,会对里面的值进行遍历。

没有使用yield*

let delegatedIterator = function*(){
  yield 'hello';
  yield 'bye'
}
let delegatingIterator = function*(){
  yield 'generator'
  yield delegatedIterator()
  yield 'finally'
}
let f = delegatingIterator()
for(let value of f){
  console.log(value)
}
//generator
//Object [Generator] {}
//finally

使用yield*

let delegatedIterator = function*(){
  yield 'hello';
  yield 'bye'
}
let delegatingIterator = function*(){
  yield 'generator'
  yield* delegatedIterator()
  yield 'finally'
}
let f = delegatingIterator()
for(let value of f){
  console.log(value)
}
// generator
// hello
// bye
// finally
const iter1 = function*(){
  yield 1;
  yield 2;
}
const iter2 = function*(){
  yield 3;
  yield 4;
}
let iterobj = function*(){
  yield* iter1();
  // yield* iter2();
  for(let value of iter2()){
    yield value
  }
}let f = iterobj()
for(let value of f){
  console.log(value)
}
//相当于generator里面使用for...of遍历 
  const a =  yield* iter1();
  //可以接收generator函数return的值

yield后面跟着的数据结构如果支持原生遍历器会返回这个数据结构的原值,数组跟字符串等,都会返回自身

只有用yield*才能对这个数据结构进行遍历

const f = function*(){
  console.log('aaaaa')
   yield* [1,2,3,4,5,6]
  console.log('a')
}
const iterobj = f()
for(let value of iterobj){
  console.log(value)
}
//遍历数组
function *foo(){
  yield 1;
  yield 2;
  return 'foo'
}
function *bar(){
  yield 0;
  const v = yield* foo()
  //执行yield,但是这句本身不算yield表达式,下个next指向的是yield 4
  console.log(v)
  yield 4
}
const f = bar()
f.next()
f.next()
f.next()
console.log(f.next())

使用yield*扁平化数组

function* itertree(arr){
  if(Array.isArray(arr)){
    for(let i = 0;i<arr.length;i++){
      yield* itertree(arr[i])
    }
  }
  else{
    yield arr
  }
}

for...of循环遍历generator函数时next调用的次数与循环的次数是否一致?

使用for...of循环会返回yield后面的表达式的值作为value。同时也会执行下一个yield之前的代码,而使用next方法对迭代器进行迭代时,一个next只会执行yield之前的代码

const f = function*(){
  yield 1;
  let a  = yield 2;
  console.log('aa')
  return 3
}
let iterobj = f()
let iterobj2 = f()
for(let value of iterobj2){
  console.log(value)
  if(value === 2){
    iterobj2.return()
      //返回值是2的时候直接中止这个遍历器
  }
}
//1   2
//结构类似于for循环,next是第三个参数,即i++,是必定会执行的,然后在进行下一个循环前判断对应的done是否为true,如果是则退出循环
//在执行完获取done,value的next方法之后,会再执行一次next,用来判断下次是否要进行循环

类似for...of调用next的实现

for(var {done,value} = iterobj.next();!done;){
      //dosomething
  console.log(value)
  var {done,value} = iterobj.next()
}
const arr = [1,2,3,4,5,6,7,8,9]
const arritor = arr[Symbol.iterator]()
for(var {done,value} = arritor.next();!done;){
  //dosomething
  console.log(value)
  var {done,value} = arritor.next()
}
//妙啊

Generator实现状态机

function *gen(){
  while(true){
    yield 'lock'
    yield 'unlock'
  }
}
const f = gen()
console.log(f.next().value)
console.log(f.next().value)
console.log(f.next().value)

状态不会被非法篡改且不用占用全局变量

协程

线程之间可以交换执行权。

一个函数执行到一半可以暂停执行,将执行权交给另外一个函数。

以多占用内存代价实现了多任务的并行运行。

使用generator进行异步处理

处理异步数据,改写回调函数

//使用generatro进行异步处理
function* gen(){
  const url = 'http://localhost:8081/moment/'
  const result = yield axios.get(url)
  console.log(result.data)
}
const f = gen()
const result = f.next()
result.value.then((data)=>{
  f.next(data)
})

co模块

使用co模块可以自动执行generator函数

const co = require('co')
const gen = function*(){
  const url = 'http://localhost:8081/moment/1'
  const url2 = 'http://localhost:8081/moment/3'
  const result = yield axios.get(url)
  const result2 = yield axios.get(url2)
  console.log(result.data)
  console.log(result2.data)
}
co(gen).then(()=>{
  console.log('generator函数执行完成')
})

co模块返回的是一个promise对象

使用co模块的条件是。yield后面跟的必须是promise对象

Generator是一个异步操作的容器,自动执行需要一种机制,当异步操作有了结果之后,就自动交还执行权

通过回调函数或者promise对象可以做到这一点,在回调里面交还执行权或者在promise.then里面交还执行权

0

评论区