好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

ES6+ Generator 基础

ES6+ Generator 基础

1. 前言

前面我们花了三节深入地学习了 ES6 的异步 解决方 案 Promise,本节学习的 生成 器也是为了 解决 异步而生的,但是它的出发思路和 Promise 截然不同。

上节我们学习了 ES6 中 迭代 的相关 内容 ,并实现了 一个 迭代器。我们知道实现 一个 迭代器,我们需要手动 添加 对象的 Symbol.i tera tor 属性 ,并需要实现 next 方法 。那么有没有什么可以帮助我们 自动 实现迭代器呢?ES6 给出了 生成 器的 方法 来满足我们的需求。我们不需要在对象上 添加 Symbol.i tera tor 属性 ,使用 生成 器 函数 就可以实现迭代器的 功能 。本节我们将学习 生成 器的相关概念和基础 用法 。

生成 器是 一个 灵活的结构,能使得 一个 函数 块内部暂停和恢复 代码 执行的能力。在实际应用中,使用 生成 器可以 自定义 迭代器和协程。

2. 生成 器对象和 生成 器 函数

有些概念是我们必须要理解的,前面在学习迭代器的时候,我们学习了迭代协议和迭代器协议,实现 一个 迭代器需要满足这两个协议才算是 一个 真正的迭代器。而本节的 生成 器和 生成 器 函数 也是如此,我们也需要知道 生成 器对象和 生成 器 函数 概念和它们直接的关系。

Generator 就是我们说的 生成 器,它包含两个概念 生成 器对象和 生成 器 函数 。首先,要理解的是 生成 器对象和迭代器的关系, 生成 器对象是遵守迭代协议和迭代器协议实现的 I tera ble 接口,可以理解 生成 器对象其实也是 一个 迭代器;然后,我们需要理解什么是 生成 器 函数 , 生成 器 函数 是由 function * 来定义的,并且返回结果是 一个 Generator 对象。

生成 器是 一个 特殊的 函数 ,在 调用 后会返回 一个 生成 器对象,这个 生成 器对象是遵守可迭代协议和迭代器协议实现的 I tera ble 接口。 生成 器可以使用 yield 关键字来暂停执行的 生成 器 函数 :

  function  *   generator  (  )   { 
   yield   'a'  ; 
   yield   'b'  ; 
 } 

 var  gen  =   generator  (  )  ; 	 // Object [Generator] {} 
 

2.1 Generator.prototype.next()

生成 器的 next () 方法 和迭代器返回的结果是一样的,返回了 一个 包含 属性 done 和 value 的对象,该 方法 也可以通过接受 一个 参数用以向 生成 器传值。

使用 yield 返回的值会被迭代器的 next () 方法 捕获:

  var  gen  =   generator  (  )  ; 

gen .  next  (  ) 	 // {value: 'a', done: false} 
gen .  next  (  ) 	 // {value: 'b', done: false} 
gen .  next  (  ) 	 // {value: undefined, done: true} 
 

从上面 代码 的执行结果可以看出, 生成 器 函数 在执行后会返回 一个 生成 器对象,这个 生成 器对象满足迭代协议和迭代器协议,所以我们可以去手动 调用 它的 next () 方法 去 获取 每一步的返回值。从这里可以看出, 生成 器其实就是迭代器的 一个 应用,并且这个应用会在异步中大放异彩。

2.2 Generator.prototype.return()

return() 方法 返回给定的值并结束 生成 器。

  var  gen  =   generator  (  )  ; 

gen .  next  (  )  ;          // { value: 'a', done: false } 
gen .  return  (  "imooc"  )  ;   // { value: "imooc", done: true } 
gen .  next  (  )  ;          // { value: undefined, done: true } 
 

另外,如果对已经完成状态的 生成 器 调用 return(value) 则 生成 器会一直保持在完成状态,如果出入参数, value 会设置成传入的参数, done 的值不变:

  var  gen  =   generator  (  )  ; 

gen .  next  (  )  ;   // { value: 1, done: false } 
gen .  next  (  )  ;   // { value: 2, done: false } 
gen .  next  (  )  ;   // { value: undefined, done: true } 
gen .  return  (  )  ;   // { value: undefined, done: true } 
gen .  return  (  )  ;   // { value: 1, done: true } 
 

2.2 Generator.prototype.throw()

throw() 方法 用来向 生成 器抛出异常,并恢复 生成 器的执行,返回带有 done 及 value 两个 属性 的对象。

  function  *   generator  (  )   { 
   while  (  true  )   { 
     try   { 
        yield   'imooc' 
     }   catch  ( e )   { 
      console .  log  (  "Error caught!"  )  ; 
     } 
   } 
 } 
 var  gen  =   generator  (  )  ; 
gen .  next  (  )  ;   // { value: "imooc", done: false } 
gen .  throw  (  new  @H_768_ 419 @Error  (  "error"  )  )  ;   // "Error caught!" 
 

3. Generator 案例

3.1 类数组转化

将 一个 类数组转化为 一个 真正的数组方式有很多,ES6 提供了 Array.from() 可以将类数组转化为数组 。另外在一些 函数 中可以使用 [...argument] 的方式转化类数组。

  function   fn  (  )   { 
   const  arg  =   [  ... arguments ]  ; 
  console .  log  ( arg )  ; 
 } 
 fn  (  ,   ,   )  ; 	 // [1, 2, 3] 
 

当然我们知道类数组的定义,所以我们自己定义 一个 类数组,看能不能使用展开运算符将类数组转化为数组:

  const  likeArr  =   { 
   :   , 
   :   , 
  length :   , 
 } 
console .  log  (  [  ... likeArr ]  )  ; 	 // Uncaught TypeError: likeArr is not i tera ble 
 

上面 代码 中我们定义了 一个 类数组,但是使用展开运算符报错了, 提示 我们 likeArr 不是 一个 迭代器。因为在 函数 中类数组是内部帮我们实现了迭代器的 功能 ,而我们自己定义的类数组是不具有迭代器 功能 的,那我们来自己实现 一个 :

 likeArr [ Symbol . i tera tor ]   =   function  (  )   { 
   let  index  =   ; 
   return   { 
    next :   (  )   =>   { 
       return   {  value :   this  [ index ]  ,  done :  index ++   ===   this  . length } 
     } 
   } 
 } 
console .  log  (  [  ... likeArr ]  )  ; 	 // [1, 2] 
 

上面的 代码 我们在 likeArr 对象上定义了 Symbol.i tera tor 它具有迭代 功能 。上面 代码 中我们需要手动地去实现 next () 方法 ,这比较麻烦,那能不能简化一下呢?我们的 生成 器 函数 就出场了:

 likeArr [ Symbol . i tera tor ]   =   function  *   (  )   { 
   let  index  =   ; 
   while   ( index  !==   this  . length )   { 
     yield   this  [ index ++  ]  ; 
   } 
 } 
console .  log  (  [  ... likeArr ]  )  ; 	 // [1, 2] 
 

上面的 代码 使用了 生成 器 函数 ,并且没有去手动实现 next () 方法 ,从这里我们也能很清楚地知道迭代器和 生成 器的关系。而且使用 生成 器 函数 更加简单方便。

3.2 单步 获取 质数

还有 一个 案例是面试中经常会考到的:

题目:实现 一个 函数 ,每次 调用返回 下 一个 质数,要求不使用 全局变量 ,且 函数 本身不接受任何参数

从题目的要求可以知道,这个 函数 每次 调用 都会返回 一个 质数,也就是说每次 调用 后都会返回 一个 函数 。

首先我们定义 一个 判断 一个 数是否为质数的 方法 :

  function   isPrime  ( num )   { 
   for   (  let  i  =   ;  i  <=  Math .  sqrt  ( num )  ;  i ++  )   { 
     if   ( num  %  i  ===   )   { 
       return   false 
     } 
   } 
   return   true 
 } 
 

传统的方式是使用闭包 方法 来 解决 :

  function   primeHandler  (  )   { 
   let  prime  =  
   return   (  )   =>   { 
     while   (  true  )   { 
      prime ++ 
       if   (  isPrime  ( prime )  )   { 
         return  prime
       } 
     } 
   } 
 } 

 const  getPrime  =   primeHandler  (  ) 
console .  log  (  getPrime  (  )  )  ; 	 // 2 
console .  log  (  getPrime  (  )  )  ; 	 // 3 
console .  log  (  getPrime  (  )  )  ; 	 // 5 
 

既然是单步执行的,那么我们就可以使用迭代器方式实现:

  var  prime  =   {  } 
prime [ Symbol . i tera tor ]   =   function  (  )   { 
   let  prime  =   ; 
   return   { 
     next  (  )   { 
       while  (  true  )   { 
        prime ++ 
         if   (  isPrime  ( prime )  )   { 
           return  prime ; 
         } 
       } 
     } 
   } 
 } 
 var  getPrime  =  prime [ Symbol . i tera tor ]  (  )  . next ; 
console .  log  (  getPrime  (  )  )  ; 	 // 2 
console .  log  (  getPrime  (  )  )  ; 	 // 3 
 

上 一个 实例我们知道实现迭代器的方式是很麻烦的,可以使用 生成 器 函数 去替代迭代器的 功能 ,所以上面的 代码 可以使用 生成 器 函数 改造如下:

  function  *   primeGenerator   (  )   { 
   let  prime  =  
   while   (  true  )   { 
    prime ++ 
     if   (  isPrime  ( prime )  )   { 
       yield  prime
     } 
   } 
 } 

 var  getPrime  =   primeGenerator  (  )  .  next  (  )  . value
console .  log  (  getPrime  (  )  )  ; 	 // 2 
console .  log  (  getPrime  (  )  )  ; 	 // 3 
 

4. 小结

本节我们主要学习了 生成 器的概念和 用法 ,需要 生成 器对象是由 生成 器 函数 返回的结果, 生成 器对象是遵守迭代协议和迭代器协议实现的 I tera ble 接口。 生成 器其实就是对迭代器的应用。另外,通过两个案例更加深刻地理解了 生成 器的应用场景,对比了 生成 器和迭代器的不同。

查看更多关于ES6+ Generator 基础的详细内容...

  阅读:42次

上一篇

下一篇

第1节:ES6+ 简介    第2节:ES6 环境配置    第3节:ES6+ let    第4节:ES6+ const    第5节:ES6+ 展开语法    第6节:ES6+ 剩余参数    第7节:ES6+ 解构赋值    第8节:ES6+ 模版字符串    第9节:ES6+ 箭头函数    第10节:ES6+ 数值扩展    第11节:ES6+ isFinite()&isNaN()    第12节:ES6+ Number 对象的方法    第13节:ES6+ Math 对象的扩展    第14节:ES6+ includes()    第15节:ES6+ 字符串的扩展    第16节:ES6+ startsWith()    第17节:ES6+ endsWith()    第18节:ES6+ repeat()    第19节:ES6+ padStart()    第20节:ES6+ padEnd()    第21节:ES6+ trim()    第22节:ES6+ Array.from()    第23节:ES6+ of()    第24节:ES6+ find()和findIndex()    第25节:ES6+ copyWithin()    第26节:ES6+ fill()    第27节:ES6+ isArray()    第28节:ES6+ 对象的扩展    第29节:ES6+ flat()    第30节:ES6+ 可选链操作符    第31节:ES6+ Object.is()    第32节:ES6+ Object.assign()    第33节:ES6+ Object.keys()    第34节:ES6+ Object.values()    第35节:ES6+ Object.entries()    第36节:ES6+ 数据结构扩展    第37节:ES6+ Set    第38节:ES6+ WeakSet    第39节:ES6+ Map    第40节:ES6+ WeakMap    第41节:ES6+ Symbol    第42节:ES6+ for...of    第43节:ES6+ 迭代协议    第44节:ES6+ 实现一个简版的 Promise    第45节:ES6+ Promise 基础    第46节:ES6+ Promise 进阶    第47节:ES6+ Generator 基础    第48节:ES6+ Generator 函数应用    第49节:ES6+ async/await    第50节:ES6+ Class 前置知识    第51节:ES6+ Class    第52节:ES6+ Proxy    第53节:ES6+ Reflect(一)    第54节:ES6+ Reflect(二)    第55节:ES6+ 模块化(一)    第56节:ES6+ 模块化(二)    第57节:ES6实战1-实现Vue3 reactive 源码    第58节:ES6实战2-实现 Vue3 effect 源码    第59节:ES6 实战2-封装请求    第60节:ES6+ 实战3-代码整洁之道    第61节:ES6 Map原理分析    第62节:ES6module语法加载importexport    第63节:ES6的循环与可迭代对象示例详解