日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

批量生成数组_JavaScript【重温基础】13.迭代器和生成器

發布時間:2024/1/23 javascript 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 批量生成数组_JavaScript【重温基础】13.迭代器和生成器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文是 重溫基礎 系列文章的第十三篇。
今日感受:每次自我年終總結,都會有各種情緒和收獲。

本章節復習的是JS中的迭代器和生成器,常常用來處理集合。

前置知識:
JavaScrip已經提供多個迭代集合的方法,從簡單的for循環到map()和filter()。
迭代器和生成器將迭代的概念直接帶入核心語言,并提供一種機制來自定義for...of循環的行為。

本文會將知識點分為兩大部分,簡單介紹和詳細介紹:
簡單介紹,適合基礎入門會使用的目標;
詳細介紹,會更加深入的做介紹,適合理解原理;

1. 概述

當我們使用循環語句迭代數據時,需初始化一個變量來記錄每一次迭代在數據集合中的位置:

let a = ["aaa","bbb","ccc"]; for (let i = 0; i< a.length; i++){console.log(a[i]); }

這邊的i就是我們用來記錄迭代位置的變量,但是在ES6開始,JavaScrip引入了迭代器這個特性,并且新的數組方法和新的集合類型(如Set集合與Map集合)都依賴迭代器的實現,這個新特性對于高效的數據處理而言是不可或缺的,在語言的其他特性中也都有迭代器的身影:新的for-of循環、展開運算符(...),甚至連異步編程都可以使用迭代器。

本文主要會介紹ES6中新增的迭代器(Iterator)和生成器(Generator)。

2. 迭代器(簡單介紹)

迭代器是一種特殊對象,它具有一些專門為迭代過程設計的專有接口,所有的迭代器對象都有一個next()方法,每次調用都會返回一個結果對象。
這個結果對象,有兩個屬性: value: 表示下一個將要返回的值。 done: 一個布爾值,若沒有更多可返回的數據時,值為true,否則false。

如果最后一個值返回后,再調用next(),則返回的對象的done值為true,而value值如果沒有值的話,返回的為undefined。

ES5實現一個迭代器:

function myIterator(list){var i = 0;return {next: function(){var done = i >= list.length;var value = !done ? list[i++] : undefined;return {done : done,value : value}}} }var iterator = myIterator([1,2,3]); iterator.next(); // "{done: false, value: 1}" iterator.next(); // "{done: false, value: 2}" iterator.next(); // "{done: false, value: 3}" iterator.next(); // "{done: true, value: undefined}" // 以后的調用都一樣 iterator.next(); // "{done: true, value: undefined}"

從上面代碼可以看出,ES5的實現還是比較麻煩,而ES6新增的生成器,可以使得創建迭代器對象的過程更加簡單。

3. 生成器(簡單介紹)

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

function *myIterator(){yield 1;yield 2;yield 3; } let iterator = myIterator(); iterator.next(); // "{done: false, value: 1}" iterator.next(); // "{done: false, value: 2}" iterator.next(); // "{done: false, value: 3}" iterator.next(); // "{done: true, value: undefined}" // 以后的調用都一樣 iterator.next(); // "{done: true, value: undefined}"

生成器函數最有趣的部分是,每當執行完一條yield語句后函數就會自動停止執行,比如上面代碼,當yield 1;執行完后,便不會執行任何語句,而是等到再調用迭代器的next()方法才會執行下一個語句,即yield 2;.
使用yield關鍵字可以返回任何值和表達式,因為可以通過生成器函數批量給迭代器添加元素:

function *myIterator(list){for(let i = 0; i< list.length ; i ++){yield list[i];} }var iterator = myIterator([1,2,3]); iterator.next(); // "{done: false, value: 1}" iterator.next(); // "{done: false, value: 2}" iterator.next(); // "{done: false, value: 3}" iterator.next(); // "{done: true, value: undefined}" // 以后的調用都一樣 iterator.next(); // "{done: true, value: undefined}"

生成器的適用返回很廣,可以將它用于所有支持函數使用的地方。

4. 迭代器(詳細介紹)

4.1 Iterator迭代器概念

Iterator是一種接口,為各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口,就可以完成迭代操作(即依次處理該數據結構的所有成員)。

Iterator三個作用: 為各種數據結構,提供一個統一的、簡便的訪問接口;
使得數據結構的成員能夠按某種次序排列;
* Iterator 接口主要供ES6新增的for...of消費;

4.2 Iterator迭代過程

  • 創建一個指針對象,指向當前數據結構的起始位置。也就是說,迭代器對象本質上,就是一個指針對象。
  • 第一次調用指針對象的next方法,可以將指針指向數據結構的第一個成員。
  • 第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。
  • 不斷調用指針對象的next方法,直到它指向數據結構的結束位置。
  • 每一次調用next方法,都會返回數據結構的當前成員的信息。具體來說,就是返回一個包含value和done兩個屬性的對象。

    • value屬性是當前成員的值;
    • done屬性是一個布爾值,表示迭代是否結束;

    模擬next方法返回值:

    let f = function (arr){var nextIndex = 0;return {next:function(){return nextIndex < arr.length ?{value: arr[nextIndex++], done: false}:{value: undefined, done: true}}} }let a = f(['a', 'b']); a.next(); // { value: "a", done: false } a.next(); // { value: "b", done: false } a.next(); // { value: undefined, done: true }

    4.3 默認Iterator接口

    若數據可迭代,即一種數據部署了Iterator接口。
    ES6中默認的Iterator接口部署在數據結構的Symbol.iterator屬性,即如果一個數據結構具有Symbol.iterator屬性,就可以認為是可迭代。 Symbol.iterator屬性本身是函數,是當前數據結構默認的迭代器生成函數。執行這個函數,就會返回一個迭代器。至于屬性名Symbol.iterator,它是一個表達式,返回Symbol對象的iterator屬性,這是一個預定義好的、類型為 Symbol 的特殊值,所以要放在方括號內(參見《Symbol》一章)。

    原生具有Iterator接口的數據結構有: Array Map Set String TypedArray 函數的 arguments 對象 * NodeList 對象

    4.4 Iterator使用場景

    • (1)解構賦值
      對數組和 Set 結構進行解構賦值時,會默認調用Symbol.iterator方法。
    let a = new Set().add('a').add('b').add('c'); let [x, y] = a; // x = 'a' y = 'b' let [a1, ...a2] = a; // a1 = 'a' a2 = ['b','c']
    • (2)擴展運算符
      擴展運算符(...)也會調用默認的 Iterator 接口。
    let a = 'hello'; [...a]; // ['h','e','l','l','o']let a = ['b', 'c']; ['a', ...a, 'd']; // ['a', 'b', 'c', 'd']
    • (2)yield*yield*后面跟的是一個可迭代的結構,它會調用該結構的迭代器接口。
    let a = function*(){yield 1;yield* [2,3,4];yield 5; }let b = a(); b.next() // { value: 1, done: false } b.next() // { value: 2, done: false } b.next() // { value: 3, done: false } b.next() // { value: 4, done: false } b.next() // { value: 5, done: false } b.next() // { value: undefined, done: true }
    • (4)其他場合
      由于數組的迭代會調用迭代器接口,所以任何接受數組作為參數的場合,其實都調用了迭代器接口。下面是一些例子。
    • for...of
    • Array.from()
    • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
    • Promise.all()
    • Promise.race()

    4.5 for...of循環

    只要數據結構部署了Symbol.iterator屬性,即具有 iterator 接口,可以用for...of循環迭代它的成員。也就是說,for...of循環內部調用的是數據結構的Symbol.iterato方法。使用場景: for...of可以使用在數組,Set和Map結構,類數組對象,Genetator對象和字符串。

    • 數組 for...of循環可以代替數組實例的forEach方法。
    let a = ['a', 'b', 'c']; for (let k of a){console.log(k)}; // a b ca.forEach((ele, index)=>{console.log(ele); // a b cconsole.log(index); // 0 1 2 })

    與for...in對比,for...in只能獲取對象鍵名,不能直接獲取鍵值,而for...of允許直接獲取鍵值。

    let a = ['a', 'b', 'c']; for (let k of a){console.log(k)}; // a b c for (let k in a){console.log(k)}; // 0 1 2
    • Set和Map
      可以使用數組作為變量,如for (let [k,v] of b){...}。
    let a = new Set(['a', 'b', 'c']); for (let k of a){console.log(k)}; // a b clet b = new Map(); b.set('name','leo'); b.set('age', 18); b.set('aaa','bbb'); for (let [k,v] of b){console.log(k + ":" + v)}; // name:leo // age:18 // aaa:bbb
    • 類數組對象
    // 字符串 let a = 'hello'; for (let k of a ){console.log(k)}; // h e l l o// DOM NodeList對象 let b = document.querySelectorAll('p'); for (let k of b ){k.classList.add('test'); }// arguments對象 function f(){for (let k of arguments){console.log(k);} } f('a','b'); // a b
    • 對象
      普通對象不能直接使用for...of會報錯,要部署Iterator才能使用。
    let a = {a:'aa',b:'bb',c:'cc'}; for (let k in a){console.log(k)}; // a b c for (let k of a){console>log(k)}; // TypeError

    4.6 跳出for...of

    使用break來實現。

    for (let k of a){if(k>100)break;console.log(k); }

    5. 生成器(詳細介紹)

    5.1 基本概念

    Generator生成器函數是一種異步編程解決方案。 原理:
    執行Genenrator函數會返回一個遍歷器對象,依次遍歷Generator函數內部的每一個狀態。Generator函數是一個普通函數,有以下兩個特征:function關鍵字與函數名之間有個星號;
    函數體內使用yield表達式,定義不同狀態;

    通過調用next方法,將指針移向下一個狀態,直到遇到下一個yield表達式(或return語句)為止。簡單理解,Generator函數分段執行,yield表達式是暫停執行的標記,而next恢復執行。

    function * f (){yield 'hi';yield 'leo';return 'ending'; } let a = f(); a.next(); // {value: 'hi', done : false} a.next(); // {value: 'leo', done : false} a.next(); // {value: 'ending', done : true} a.next(); // {value: undefined, done : false}

    5.2 yield表達式

    yield表達式是暫停標志,遍歷器對象的next方法的運行邏輯如下:
    1. 遇到yield就暫停執行,將這個yield后的表達式的值,作為返回對象的value屬性值。
    2. 下次調用next往下執行,直到遇到下一個yield。
    3. 直到函數結束或者return為止,并返回return語句后面表達式的值,作為返回對象的value屬性值。
    4. 如果該函數沒有return語句,則返回對象的value為undefined 。

    注意:
    * yield只能用在Generator函數里使用,其他地方使用會報錯。

    // 錯誤1 (function(){yiled 1; // SyntaxError: Unexpected number })()// 錯誤2 forEach參數是個普通函數 let a = [1, [[2, 3], 4], [5, 6]]; let f = function * (i){i.forEach(function(m){if(typeof m !== 'number'){yield * f (m);}else{yield m;}}) } for (let k of f(a)){console.log(k) }
    • yield表達式如果用于另一個表達式之中,必須放在圓括號內。
    function * a (){console.log('a' + yield); // SyntaxErroconsole.log('a' + yield 123); // SyntaxErroconsole.log('a' + (yield)); // okconsole.log('a' + (yield 123)); // ok }
    • yield表達式用做函數參數或放在表達式右邊,可以不加括號。
    function * a (){f(yield 'a', yield 'b'); // oklei i = yield; // ok }

    5.3 next方法

    yield本身沒有返回值,或者是總返回undefined,next方法可帶一個參數,作為上一個yield表達式的返回值。

    function * f (){for (let k = 0; true; k++){let a = yield k;if(a){k = -1};} } let g =f(); g.next(); // {value: 0, done: false} g.next(); // {value: 1, done: false} g.next(true); // {value: 0, done: false}

    這一特點,可以讓Generator函數開始執行之后,可以從外部向內部注入不同值,從而調整函數行為。

    function * f(x){let y = 2 * (yield (x+1));let z = yield (y/3);return (x + y + z); } let a = f(5); a.next(); // {value : 6 ,done : false} a.next(); // {value : NaN ,done : false} a.next(); // {value : NaN ,done : true} // NaN因為yeild返回的是對象 和數字計算會NaNlet b = f(5); b.next(); // {value : 6 ,done : false} b.next(12); // {value : 8 ,done : false} b.next(13); // {value : 42 ,done : false} // x 5 y 24 z 13

    5.4 for...of循環

    for...of循環會自動遍歷,不用調用next方法,需要注意的是,for...of遇到next返回值的done屬性為true就會終止,return返回的不包括在for...of循環中。

    function * f(){yield 1;yield 2;yield 3;yield 4;return 5; } for (let k of f()){console.log(k); } // 1 2 3 4 沒有 5

    5.5 Generator.prototype.throw()

    throw方法用來向函數外拋出錯誤,并且在Generator函數體內捕獲。

    let f = function * (){try { yield }catch (e) { console.log('內部捕獲', e) } }let a = f(); a.next();try{a.throw('a');a.throw('b'); }catch(e){console.log('外部捕獲',e); } // 內部捕獲 a // 外部捕獲 b

    5.6 Generator.prototype.return()

    return方法用來返回給定的值,并結束遍歷Generator函數,如果return方法沒有參數,則返回值的value屬性為undefined。

    function * f(){yield 1;yield 2;yield 3; } let g = f(); g.next(); // {value : 1, done : false} g.return('leo'); // {value : 'leo', done " true} g.next(); // {value : undefined, done : true}

    5.7 next()/throw()/return()共同點

    相同點就是都是用來恢復Generator函數的執行,并且使用不同語句替換yield表達式。
    * next()將yield表達式替換成一個值。

    let f = function * (x,y){let r = yield x + y;return r; } let g = f(1, 2); g.next(); // {value : 3, done : false} g.next(1); // {value : 1, done : true} // 相當于把 let r = yield x + y; // 替換成 let r = 1;
    • throw()將yield表達式替換成一個throw語句。
    g.throw(new Error('報錯')); // Uncaught Error:報錯 // 相當于將 let r = yield x + y // 替換成 let r = throw(new Error('報錯'));
    • next()將yield表達式替換成一個return語句。
    g.return(2); // {value: 2, done: true} // 相當于將 let r = yield x + y // 替換成 let r = return 2;

    5.8 yield* 表達式

    用于在一個Generator中執行另一個Generator函數,如果沒有使用yield*會沒有效果。

    function * a(){yield 1;yield 2; } function * b(){yield 3;yield * a();yield 4; } // 等同于 function * b(){yield 3;yield 1;yield 2;yield 4; } for(let k of b()){console.log(k)} // 3 // 1 // 2 // 4

    5.9 應用場景

  • 控制流管理
    解決回調地獄:
  • // 使用前 f1(function(v1){f2(function(v2){f3(function(v3){// ... more and more})}) })// 使用Promise Promise.resolve(f1).then(f2).then(f3).then(function(v4){// ...},function (err){// ...}).done();// 使用Generator function * f (v1){try{let v2 = yield f1(v1);let v3 = yield f1(v2);let v4 = yield f1(v3);// ...}catch(err){// console.log(err)} } function g (task){let obj = task.next(task.value);// 如果Generator函數未結束,就繼續調用if(!obj.done){task.value = obj.value;g(task);} } g( f(initValue) );
  • 異步編程的使用 在真實的異步任務封裝的情況:
  • let fetch = require('node-fetch'); function * f(){let url = 'http://www.baidu.com';let res = yield fetch(url);console.log(res.bio); } // 執行該函數 let g = f(); let result = g.next(); // 由于fetch返回的是Promise對象,所以用then result.value.then(function(data){return data.json(); }).then(function(data){g.next(data); })

    參考資料

    1.MDN 迭代器和生成器
    2.ES6中的迭代器(Iterator)和生成器(Generator)

    本部分內容到這結束

    歡迎關注我的微信公眾號【前端自習課】

    總結

    以上是生活随笔為你收集整理的批量生成数组_JavaScript【重温基础】13.迭代器和生成器的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。