深入理解ES6 008【學習筆記】

迭代器(Iterator)和生成器(Generator)

這個新特性對於高效的數據處理而言是不可或缺的,你也會發現在語言的其他特性中也都有迭代器的身影:新的for-of循環、展開運算符(...)、甚至連異步編程都可以使用迭代器。

迭代器是一種特殊的對象,它具有一些專門為迭代過程設計的專有接口,所有的迭代器對象都有一個next()方法,每次調用都返回一個結果對象(形如:{value:true|false,next(){}})。

function createIterator(items){
  var i = 0;
  return {
    next:function(){
      let done = (i>=items.length);
      let value = !done?items[i++];undefined;
      return {
        done,
        value
      }
    }
  }
}
var iterator = createIterator([1,2,3])
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:3,done:false}
console.log(iterator.next()); // {value:undefined,done:true}
// 之後所有調用都會返回相同內容
// {value:undefined,done:true}

生成器

生成器是一種返回迭代器的函數,通過function關鍵字後的星號(*)來表示,函數中會用的新的關鍵字yield。星號可以緊挨著function關鍵字,也可以在中間添加一個空格

// 生成器
function *createIteator(){
  yield 1;
  yield 2;
  yield 3;
}

// 生成器的調用方式與普通函數相同,只不過返回的是一個迭代器
let iterator = createIteator();

console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

生成器函數最有趣的部分大概是,每當執行完一條yield語句後函數就會自動停止執行。執行yield 1之後,函數便不再執行其他任何語句,直到再次調用迭代器的next()才會繼續執行yield 2

使用yield關鍵字可以返回任何值或表達式,所以可以通過生成器函數批量地給迭代器添加元素,也可以在循環中使用yield關鍵字

yield後的面的值會放在迭代器返回值的value中

function *createIterator(items){
  for(let i=0;i<items.length;i++){
    yield items[i];
  }
}
let iterator = createIterator([1,2,3])

console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next()) // {value:2,done:false}
console.log(iterator.next()) // {value:3,done:false}
console.log(iterator.next()) // {value:undefined,done:true}
// 之後所有的調用都會返回相同內容
console.log(iterator.next()) // {value:undefined,done:true}

生成器函數表達式

let createIterator = function *(items){
  for(let i=0;i<items.length;i++){
    yield items[i];
  }
}
let iterator = createIterator([1,2,3])

console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next()) // {value:2,done:false}
console.log(iterator.next()) // {value:3,done:false}
console.log(iterator.next()) // {value:undefined,done:true}
// 之後所有的調用都會返回相同內容
console.log(iterator.next()) // {value:undefined,done:true}

生成器對象的方法

let o={
  createIterator:function *(items){
    for(let i=0;i<items.length;i++){
      yield items[i];
    }
  }
}
// 或簡寫
let o1={
  *createIterator(items){
    for(let i=0;i<items.length;i++){
      yield items[i];
    }
  }
}

可迭代對象和for-of循環

可迭代對象具有Symbol.iterator屬性,是一種與迭代器密切相關對象。Symbol.iterator通過指定的函數可以返回一個作用於附屬對象的迭代器。ES6所有集合對象(數組、Set集合及Map集合)和字符串都是可迭代對象,這些對象都有默認的迭代器。

由於生成器默認會為Symbol.iterator屬性賦值,因此所有通過生成器創建的迭代器都是可迭代對象。

訪問默認迭代器

let values = [1,2,3];
let iterator = values[Symbol.iterator]();

console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next()) // {value:2,done:false}
console.log(iterator.next()) // {value:3,done:false}
console.log(iterator.next()) // {value:undefined,done:true}

由於具有Symbol.iterator屬性的對象都有默認的迭代器,因此可以用它來檢測對象是否為可迭代對象

function isIterator(object){
  return typeof object[Symbol.iterator] === "function";
}
console.log(isIterator([1,2,3])); // true
console.log(isIterator("Hello")); // true
console.log(isIterator(new Map())); // true
console.log(isIterator(new Set())); // true
console.log(isIterator(new WeakMap())); // false
console.log(isIterator(new WeakSet())); // false

創建可迭代對象

默認情況下,開發者定義的定義都是不可迭代對象,但如果給Symbol.iterator屬性添加一個生成器

let collection = {
  items:[],
  *[Symbol.iterator](){
    for(let item of this.items){
      yield items;
    }
  }
}
collection.items.push(1)
collection.items.push(2)
collection.items.push(3)

for(let x of collection){
  console.log(x)
}

內建迭代器 集合對象迭代器

  • entries()
  • values()
  • keys()

字符串迭代器

var message = "A 𠮷 B";

for(let c of message){
  console.log(c)
}

NodeList迭代器

var divs = document.getElementByTagName("div");

for(let div of divs){
  console.log(div.id)
}

展開運算符與非數組可迭代對象

let set = new Set([1,2,3,3,3,4,5]),
array = [...set];
console.log(array); // [1,2,3,4,5]

let map = new Map([["name","Nicholas"],["age",25]]),
array = [...map];

console.log(array) // [["name","Nicholas"],["age",25]]

高級迭代器功能

可以用迭代器的next()方法的返回值,也可以在生成器內部使用yield關鍵字來生成值,如果給迭代器的next()方法傳遞參數,則這個參數的值就會替代生成器內部上一條yield語句的返回值。而如果要實現更多像異步編程這樣的高級功能

function *createIterator() {
  let first = yield 1;
  let second = yield first + 2; 
  yield second +3;
}

let iterator = createIterator();

console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next(4)) // {value:6,done:false}
console.log(iterator.next(5)) // {value:8,done:false}
console.log(iterator.next()) // {value:undefined,done:true}

第一次調用next()方法時無論傳入甚麼參數都會被丟棄。由於傳給next()方法的參數會替代上一次yield的返回值,而在第一次調用next()方法前不會執行任何yield語句,因此第一次調用next()方法時傳遞參數是毫無意義的。

這裡注意yield xxxlet a = yield xxx
yield xxx 執行後會把yield 後面的表達式的值添加到迭代對象的value中,而let a的值是上次next傳參數的值。

function *createIterator(){
  let first = yield 1;
  let second;
  try{
    second = yield first+2; //
  }catch(ex) {
    second = 6;
  }
  yield second+3;
}

let iterator = createIterator();
console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next(4)) // {value:6,done:false}
console.log(iterator.throw(new Error('Boom'))) // {value:69,done:false}
console.log(iterator.next()) //{value:undefined,done:true}

這裡注意:調用throw()方法後也會像調用next()方法一樣返回一個結果對象。由於成生成器內部捕獲了這個錯誤,因而會繼續執行下一條yield語句。

如此一來,next()throw()就像是迭代器的兩條指令,調用next()方法命令迭代器繼續執行,調用throw()方法也會命令迭代器繼續執行,但同時拋出一個錯誤,在此之後的執行過程取決於生成器內部的代碼(是否對error try-catch)

生成器的返回語句

由於生成器是函數,因此可以通過return語句提前退出函數執行,對於最後一次next()方法調用,可以主動為其指定一個返回值,在之前我們最後一次調用返回的都是undefined,正如在其他函數中那樣,你可以通過return 語句指定一個返回值。而在生成器中,return 表示所有操作已經完成,屬性done設置為true,同時如果提供一個返回值,則屬性value被設置為這個值。通過return語句指定的返回值,只會在返回對象中出現一次,在後續調用返回的對象中,value懺悔會被重置為undefined。

function *createIterator(){
  yield 1;
  return 42;
}
let iterator = createIterator()
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:42,done:true}
console.log(iterator.next()); // {value:undefined,done:true}

展開運算符與for-of循環語句會直接忽略通過return語句指定的任何返回值,只要done一變為true,就立即停止讀取其他值。返回值可以像給next()傳參一樣傳遞(委託生成器中)

委託生成器

如下:(yield語句添加一個*號)

function *createNumberIterator(){
  yield 1;
  yield 2;
}

function *createColorIterator(){
  yield "red";
  yield "green";
}

function *createCombinedIterator(){
  yield *createNmberIterator()
  yield *createColorIterator()
  yield true
}
var iterator = createCombinedIterator()
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:"red",done:false}
console.log(iterator.next()); // {value:"green",done:false}
console.log(iterator.next()); // {value:true,done:false}
console.log(iterator.next()); // {value:undefined,done:true}

有了生成器委託這個新功能,你可以進一步利用生成器的返回值來處理複雜任務:

function *createNumberIterator(){
  yield 1;
  yield 2;
  return 3;
}

function *createRepeatingIterator(count){
  for(let i=0;i<count;i++){
    yield "repeat";
  }
}

function *createCombinedIterator(){
  let result = yield *createNumberIterator()
  yield *createRepeatingIterator(result); // 返回值賦值給result 
}
var iterator = createCombinedIterator()
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:"repeat",done:false}
console.log(iterator.next()); // {value:"repeat",done:false}
console.log(iterator.next()); // {value:"repeat",done:false}
console.log(iterator.next()); // {value:undefined,done:true}
// 數值3永遠不會被返回,它只存在於生成器createCombinedIterator()的內部。如果想返回岀可以額外添加一條yield語句

yield *也可以直接應用於字符串,例如yield * "hello" 此時將使用字符串的默認迭代器

異步任務

複雜任務的異步化會帶來很多管理代碼的挑戰,由於生成器支持在函數中暫停代碼執行,因而可以深入挖掘異步處理的更多用法。簡單的回調處理還好

let fs = require('fs');
fs.readFile('config.json',function(err,contents){
  if(err){
    throw err;
  }
  doSomethingWith(contents);
  console.log("Done")
})

如果需要嵌套回調或序列化一系列的異步操作,事情會變得非常複雜。此時,生成器和yield語句就派上了用場了。

簡單任務執行器

由於執行yield語句會暫停當前函數的執行過程並等待一下次調用next()方法,因此你可以創建一個函數,在函數中調用生成器生成相應的迭代器,從而在不用回調函數的基礎上實現異步next()方法的調用(類似async-await語法糖):

function run(taskDef){
  // 創建一個無使用限制的迭代器
  let task = taskDef();

  // 開始執行任務
  let result = task.next();

  // 循環調用next()
  function step(){
    // 如果任務未完成,則繼續執行
    if(!result.done){
      result = task.next();
      step();
    }
  }

  // 開始迭代執行
  step()
}
run(function*(){
  console.log(1);
  yield;
  console.log(2);
  yield;
  console.log(3);

})

向任務執行器傳遞參數

function run(taskDef){
  // 創建一個無使用限制的迭代器
  let task = taskDef();

  // 開始執行任務
  let result = task.next();

  // 循環調用next()
  function step(){
    // 如果任務未完成,則繼續執行
    if(!result.done){
      result = task.next(result.value);
      step();
    }
  }

  // 開始迭代執行
  step()
}

run(function*(){
  let value = yield 1;
  console.log(value) // 1
  value = yield value+3;
  console.log(value) // 4
})

異步任務執行器

function run(taskDef){
  // 創建一個無使用限制的迭代器
  let task = taskDef();

  // 開始執行任務
  let result = task.next();

  // 循環調用next()
  function step(){
    // 如果任務未完成,則繼續執行
    if(!result.done){
      if(typeof result.value === 'funciton'){
        // 是函數傳回調進去
        result.value(function(err,data){
          if(err){
            result = task.throw(err)
            return ;
          }
          result = task.next(data)
          step()
        })
      } else {
        result = task.next(result.value);
        step();
      }
    }
  }

  // 開始迭代執行
  step()
}

let fs = require("fs");
function readFile(filename){
  return function(callback){
    fs.readFile(filename,callback)
  }
}

run(function*(){
  let contents = yield readFile("config.json");
  doSomethingWith(contents);
  console.log("Done")
})

主題測試文章,只做測試使用。發佈者:Walker,轉轉請注明出處:https://walker-learn.xyz/archives/4334

(0)
Walker的頭像Walker
上一篇 2025年3月8日 12:52
下一篇 2025年3月8日 12:51

相關推薦

  • Node深入淺出(聖思園教育) 002【學習筆記】

    node 的包管理機制和加載機制 npm search xxxnpm view xxxnpm install xxx nodejs 文件系統操作的 api Node.js 的 fs 模塊提供同步(Sync)與基於回調/Promise 的異步 API,可以操作本地文件與目錄。日常開發中常用的能力包括讀取、寫入、追加、刪除、遍歷目錄、監聽變化等。以下示例基於 C…

    個人 2025年11月24日
    22800
  • 深入理解ES6 011【學習筆記】

    Promise與異步編程 因為執行引擎是單線程的,所以需要跟蹤即將運行的代碼,那些代碼被放在一個任務隊列中,每當一段代碼準備執行時,都會被添加到任務隊列中,每當引擎中的一段代碼結束執行,事件循環會執行隊列中的一下個任務。 Promise相當於異步操作結果佔位符,它不會去訂閱一個事件,也不會傳遞一個回調函數給目標函數,而是讓函數返回一個Promise,就像這樣…

    個人 2025年3月8日
    1.1K00
  • Go工程師體系課 001【學習筆記】

    轉型 想在短時間系統轉到Go工程理由 提高CRUD,無自研框架經驗 拔高技術深度,做專、做精需求的同學 進階工程化,擁有良好開發規範和管理能力的 工程化的重要性 高級開的期望 良好的代碼規範 深入底層原理 熟悉架構 熟悉k8s的基礎架構 擴展知識廣度,知識的深度,規範的開發體系 四個大的階段 go語言基礎 微服務開發的(電商項目實戰) 自研微服務 自研然後重…

    個人 2025年11月25日
    27800
  • Go工程師體系課 008【學習筆記】

    訂單及購物車 先從庫存服務中將 srv 的服務代碼框架複製過來,查找替換對應的名稱(order_srv) 加密技術基礎 對稱加密(Symmetric Encryption) 原理: 使用同一個密鑰進行加密和解密 就像一把鑰匙,既能鎖門也能開門 加密速度快,適合大量數據傳輸 使用場景: 本地文件加密 數據庫內容加密 大量數據傳輸時的內容加密 內部系統間的快速通…

    個人 2025年11月25日
    19500
  • 無畏前行,拳釋力量 🥊💪

    拼搏,是一種態度 生活就像一場比賽,沒有捷徑可走,只有不斷訓練、突破、超越,才能站上屬於自己的舞台。這不僅是一種對抗,更是一種自我的覺醒——敢於迎戰,敢於挑戰,敢於成為更強的自己。 運動中的拼搏精神 無論是拳擊、跑步,還是力量訓練,每一次出拳、每一次揮汗、每一次咬牙堅持,都是對身體與心靈的磨煉。拼搏不是單純的對抗,而是一種態度——面對挑戰,不退縮;面對失敗,…

    個人 2025年2月26日
    1.3K00
簡體中文 繁體中文 English
歡迎🌹 Coding never stops, keep learning! 💡💻 光臨🌹