Iterator與Generator

来源:https://www.cnblogs.com/dtux/archive/2022/08/19/16601087.html
-Advertisement-
Play Games

Iterator Iterator 概念 Iterator 提供了一種統一的介面機制,為各種不同數據結構提供統一的訪問機制。定義 Iterator 就是提供一個具有 next() 方法的對象,每次調用 next() 都會返回一個結果對象,該結果對象有兩個屬性,value 表示當前的值,done 表示 ...


file

Iterator

Iterator 概念

Iterator 提供了一種統一的介面機制,為各種不同數據結構提供統一的訪問機制。定義 Iterator 就是提供一個具有 next() 方法的對象,每次調用 next() 都會返回一個結果對象,該結果對象有兩個屬性,value 表示當前的值,done 表示遍歷是否結束。

function makeIterator(Array){
    let index = 0;
    return {
        next: function(){
            return (
                Array.length > index ?
                {value: Array[index++]}:
                {done: true}
            )
        }
    }
}

let iterator = makeIterator(['1','2'])
console.log(iterator.next()); // {value: '1'}
console.log(iterator.next()); // {value: '2'}
console.log(iterator.next()); // {done: true}

Iterator 的作用:

  1. 為各種數據結構,提供一個統一的、簡便的訪問介面;
  2. 使得數據結構的成員能夠按某種次序排列;
  3. 供 for...of 消費

預設 Iterator 介面

ES6 提供了 for of 語句遍歷迭代器對象,我們將上述創建的迭代器使用 for of 語句遍歷一下:

let iterator = makeIterator(['1','2'])
for (let value of iterator) {
    console.log(value);
} // iterator is not iterable

結果報錯說 iterator is not iterable,這是為什麼呢?
ES6 規定預設的 Iterator 介面部署在數據結構的 Symbol.iterator 屬性中,如果一個數據結構存在 Symbol.iterator 屬性,則該數據結構可遍歷。我們將自定義的 makeIterator 改造如下:

const MakeIterator = (Array) => ({
    [Symbol.iterator](){
        let index = 0;
        return {
            next(){
                let length = Array.length;
                if(index < length){
                    return {value: Array[index++]}
                }else{
                    return {done: true}
                }
            }
        }
    }
})
for(let value of MakeIterator([1,2])){
    console.log(value)
}
// 1
// 2

Iterator 的 return()

我們為 MakeIterator 添加 return 方法,如果 for...of 迴圈提前退出(通常是因為出錯,或者有 break 語句),就會調用 return() 方法,終止遍歷。基於這一特性,如果一個對象在完成遍歷前,需要清理或釋放資源,我們可以部署 return() 方法,列入文件讀取失敗時關閉文件。

const MakeIterator = (Array) => ({
    [Symbol.iterator](){
        let index = 0;
        return {
            next(){
                let length = Array.length;
                if(index < length){
                    return {value: Array[index++]}
                }else{
                    return {done: true}
                }
            },
            return(){
                return {done: true}
            }
        }
    }
})
for(let value of MakeIterator([1, 2, 3])){
    console.log(value) // 1
    // 方式1
    break;
    // 方式2
    // throw new Error('error');
}

原生具備 Iterator 介面的數據結構

  1. 數組
  2. Set
  3. Map
  4. 類數組對象,如 arguments 對象、DOM NodeList 對象、typedArray 對象
// arguments 對象
function sum(){
    for(let value of arguments){
        console.log(value)
    }
}
sum(1,2)
// 1
// 2

// typedArray 對象
let typeArry = new Int8Array(2);
typeArry[0] = 1;
typeArry[1] = 2;
for(let value of typeArry){
    console.log(value) 
}
// 1
// 2
  1. Generator 對象
function* gen(){
    yield 1;
    yield 2;
}
for(let value of gen()){
    console.log(value)
}
  1. String

Q: 為什麼 Object 不具有原生 Iterator ?

A: 對象(Object)之所以沒有預設部署 Iterator 介面,是因為對象的哪個屬性先遍歷,哪個屬性後遍歷是不確定的。本質上,遍歷器是一種線性處理,對於任何非線性的數據結構,部署遍歷器介面,就等於部署一種線性轉換。不過,嚴格地說,對象部署遍歷器介面並不是很必要,因為這時對象實際上被當作 Map 結構使用,ES5 沒有 Map 結構,而 ES6 原生提供了。

調用 Iterator 介面的場合

  1. 解構賦值
let set = new Set().add('a').add('b').add('c');

let [x,y] = set; // x='a'; y='b'
  1. 擴展運算符
var str = 'hello';
[...str] //  ['h','e','l','l','o']

擴展運算符是調用 Iterator 介面,那麼 Object 沒有部署 Iterator 介面,為什麼也能使用 ... 運算符呢?
原因:擴展運算符分為兩種

  • 一種是用在函數參數、數組展開的場合,這種情況要求對象是可迭代的(iterable)
  • 另一種是用於對象展開,也就是 {…obj} 形式,這種情況需要對象是可枚舉的(enumerable)
let obj1 = {
    name: 'qianxun'
} 
let obj2 = {
    age: 3
}
// 數組對象是可枚舉的
let obj = {...obj1, ...obj2}
console.log(obj) //{name: 'qianxun', age: 3}

// 普通對象預設是不可迭代的
let obj = [...obj1, ...obj2]
console.log(obj) // object is not iterable

模擬實現 for of

function forOf(obj, cb){
    let iteratorValue = obj[Symbol.iterator]();
    let result = iteratorValue.next()
    while(!result.done){
        cb(result.value)
        result = iteratorValue.next()
    }
}

forOf([1,2,3], (value)=>{
    console.log(value)
})
// 1
// 2
// 3

Generator

認識 Generator

// 概念上
Generator 函數是 ES6 提供的一種非同步編程解決方案。Generator 函數是一個狀態機,封裝了多個內部狀
態;Generator 函數還是一個遍歷器對象生成函數,執行後返回一個遍歷器對象。

// 形式上
1.function 關鍵字與函數名之間有一個星號;
2.函數體內部使用 yield 表達式,定義不同的內部狀態。
function* simpleGenerator(){
    yield 1;
    yield 2;
}
simpleGenerator()

如上我們創建了一個簡單的 Generator,我們帶著兩個問題進行探究:

  1. Generator 函數運行後會發生什麼?

  2. 函數中的 yield 表達式有什麼作用?

function* simpleGenerator(){
    console.log('hello world');
    yield 1;
    yield 2;
}
let generator = simpleGenerator(); // simpleGenerator {<suspended}}
console.log(generator.next())
// hello world
// {value: 1, done: false}
console.log(generator.next())
// {value: 2, done: false}

Generator 生成器函數運行後返回一個生成器對象,而普通函數會直接執行函數內部的代碼;每次調用生成器對象的 next 方法會執行函數到下一次 yield 關鍵字停止執行,並且返回一個 {value: Value, done: Boolean} 的對象。

next 方法的參數

yield 表達式本身沒有返回值,或者說總是返回 undefined。next 方法可以帶一個參數,該參數就會被當作上一個 yield 表達式的返回值。通過 next 方法的參數,可以在 Generator 函數運行的不同階段,從外部向內部註入不同的值,從而調整函數行為。
由於 next 方法的參數表示上一個 yield 表達式的返回值,所以在第一次使用 next 方法時,傳遞參數是無效的。

function sum(x){
    return function(y){
        return x + y;
    }
}
console.log(sum(1)(2))

// 利用next傳參改寫
function* sum(x){
    let y = yield x;
    while(true){
       y = yield x + y;
    }
}

let gen = sum(2)
console.log(gen.next()) // 2
console.log(gen.next(1)) // 3
console.log(gen.next(2))  // 4

yield 表達式

yield 表達式的作用:定義內部狀態和暫停執行
yield 表達式 與 return 語句的區別

  • yield 表達式表示函數暫停執行,下一次再從該位置繼續向後執行,而 return 語句不具備位置記憶的功能
  • 一個函數里,只能執行一個 return 語句,但是可以執行多個 yield 表達式
  • 任何函數都可以使用 return 語句,yield 表達式只能用在 Generator 函數裡面,用在其他地方都會報錯
  • yield 表達式如果參與運算放在圓括弧裡面;用作函數參數或放在賦值表達式的右邊,可以不加括弧
function *gen () {
  console.log('hello' + yield) ×
  console.log('hello' + (yield)) √
  console.log('hello' + yield 1) ×
  console.log('hello' + (yield 1)) √
  foo(yield 1)  √
  const param = yield 2  √
}

基於 Generator 生成器函數中可以支持多個 yield,我們可以實現一個函數有多個返回值的場景:

function* gen(num1, num2){
    yield num1 + num2;
    yield num1 - num2;
}

let res = gen(2, 1);
console.log(res.next()) // {value: 3, done: false}
console.log(res.next()) // {value: 1, done: false}

Generator 與 Iterator 之間的關係

由於 Generator 函數就是遍歷器生成函數,因此可以把 Generator 賦值給對象的 Symbol.iterator 屬性,從而使得該對象具有 Iterator 介面。Generator 實現方式代碼更加簡潔。

let obj = {
    name: 'qianxun',
    age: 3,
    [Symbol.iterator]: function(){
        let that = this;
        let keys = Object.keys(that)
        let index = 0;
        return {
            next: function(){
                return index < keys.length ?
                {value: that[keys[index++]], done: false}:
                {value: undefined, done: true}
            }
        }
    }
}
for(let value of obj){
    console.log(value)
}

Generator:

let obj = {
    name: 'qianxun',
    age: 3,
    [Symbol.iterator]: function* (){
        let keys = Object.keys(this)
        for(let i=0; i< keys.length; i++){
            yield this[keys[i]];
        }
    }
}
for(let value of obj){
    console.log(value)
}

Generator.prototype.return()

return()方法,可以返回給定的值,並且終結遍歷 Generator 函數。

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
// 如果 return() 方法調用時,不提供參數,則返回值的 value 屬性為 undefined
g.return('foo') // { value: "foo", done: true }
g.next()        // { value: undefined, done: true }

如果 Generator 函數內部有 try...finally 代碼塊,且正在執行 try 代碼塊,那麼 return() 方法會導致立刻進入 finally 代碼塊,執行完以後,整個函數才會結束。

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }

yield* 表達式

如果想在 Generator 函數內部,調用另一個 Generator 函數。我們需要在前者的函數體內部,自己手動完成遍歷,如果函數調用多層嵌套會導致寫法繁瑣不易閱讀,ES6 提供了 yield* 表達式作為解決方法。

委托給其他生成器

function* g1() {
  yield 2;
  yield 3;
}

function* g2() {
  yield 1;
  yield* g1();
  yield 4;
}

const iterator = g2();

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: 4, done: false }
console.log(iterator.next()); // { value: undefined, done: true }

委托給其他可迭代對象

function* gen(){
    yield* [1,2,3]
}
console.log(gen().next()) // {value: 1, done: false}

Generator 函數的 this

Generator 函數返回一個遍歷器,ES6 規定這個遍歷器是 Generator 函數的實例,繼承了 Generator.prototype 對象上的方法,但無法獲取 this 上的屬性,因為這時 this 是全局對象,而不是實例對象。

function* gen(){
    this.a = 1
}
gen.prototype.say = function(){
    console.log('hi')
}
let obj = gen()
console.log(obj instanceof gen) // true
obj.say() // hi
obj.next()
console.log(obj.a) //undefined

如果想像構造函數一樣訪問實例屬性,可以修改 this 綁定到 Generator.prototype 上。

function* gen(){
    this.a = 1
}
gen.prototype.say = function(){
    console.log('hi')
}   
let obj = gen.call(gen.prototype)
console.log(obj instanceof gen) // true
obj.say() // hi
obj.next()
console.log(obj.a) //1

Generator 實現一個狀態機

function* StateMachine(state){
    let transition;
    while(true){
        if(transition === "INCREMENT"){
            state++;
        }else if(transition === "DECREMENT"){
            state--;
        }
        transition = yield state;
    }
}
const iterator = StateMachine(0);
console.log(iterator.next()); // 0
console.log(iterator.next('INCREMENT')); // 1
console.log(iterator.next('DECREMENT')); // 0

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • InnoDB導入StoneDB 此場景是利用mysqldump從InnoDB導出,然後再導入StoneDB,在導入StoneDB前,需要對導出文件做如下修改。 1)修改存儲引擎 CREATE TABLE `t_user` ( xxx ) ENGINE=InnoDB; CREATE TABLE `t_ ...
  • MySQL Server當前支持如下3種註釋風格: 以'# '開頭的單行註釋 以'-- '開頭的單行註釋 C語言風格的單行/多行註釋 如下sql腳本給出了3種註釋風格的示例: /* 這是一個 多行註釋 示例 */ select 1 from dual; select 2 from dual; # 單 ...
  • 隨著人們生活水平的提高,大家對健康越來越重視和關註,用戶在使用一些健康App時不僅想知道身高體重等基礎情況,還想瞭解一些關於心率、血氧等日常數據,方便隨時關註自身健康狀況。這時候就需要App每天關註健康數據並且記錄下來,如日常飲食、睡眠習慣,心率、血壓血糖變化和運動數據等,並且建立一份個人健康檔案, ...
  • JSON.stringify()妙用 點擊打開視頻講解更加詳細 語法:JSON.stringify(value, replacer , space) value:將要序列化成 一個JSON 字元串的值。 replacer(可選):如果該參數是一個函數,則在序列化過程中,被序列化的值的每個屬性都會經過 ...
  • 想要項目快速迭代,輪子必不可少。normalize.css,element-plus,axios,moment,vue-router,less,前端必知必會的輪子你都知道嗎? ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 uni.login(OBJECT)登錄 H5平臺登陸註意事項: 微信內嵌瀏覽器運行H5版時,可通過js sdk實現微信登陸,需要引入一個單獨的js,詳見普通瀏覽器上實現微信登陸,並非開放API,需要向微信申請,僅個別開發者有此許可權H5平臺 ...
  • 本文是深入淺出 ahooks 源碼系列文章的第九篇,該系列已整理成文檔-地址。覺得還不錯,給個 star 支持一下哈,Thanks。 今天來看看 ahooks 是怎麼封裝 cookie/localStorage/sessionStorage 的。 cookie ahooks 封裝了 useCooki ...
  • 遞歸組件 點擊打開視頻講解更加詳細 組件是可以在它們自己的模板中調用自身的。不過它們只能通過 name 選項來做這件事: name: 'unique-name-of-my-component' 當你使用 Vue.component 全局註冊一個組件時,這個全局的 ID 會自動設置為該組件的 name ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...