<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          有了 for 循環(huán),為什么還需要使用 forEach ?

          共 6103字,需瀏覽 13分鐘

           ·

          2021-12-24 20:44


          作者 | 技術(shù)直男星辰
          來源 | https://juejin.cn/post/7018097650687803422

          js中那么多循環(huán),for for...in for...of forEach,有些循環(huán)感覺上是大同小異今天我們討論下for循環(huán)和forEach的差異。我們從幾個維度展開討論:
          1. for循環(huán)和forEach的本質(zhì)區(qū)別。
          2. for循環(huán)和forEach的語法區(qū)別。
          3. for循環(huán)和forEach的性能區(qū)別。

          本質(zhì)區(qū)別

          for循環(huán)是js提出時就有的循環(huán)方法。forEach是ES5提出的,掛載在可迭代對象原型上的方法,例如Array Set Map。forEach是一個迭代器,負(fù)責(zé)遍歷可迭代對象。那么遍歷,迭代,可迭代對象分別是什么呢。
          遍歷:指的對數(shù)據(jù)結(jié)構(gòu)的每一個成員進(jìn)行有規(guī)律的且為一次訪問的行為。
          迭代:迭代是遞歸的一種特殊形式,是迭代器提供的一種方法,默認(rèn)情況下是按照一定順序逐個訪問數(shù)據(jù)結(jié)構(gòu)成員。迭代也是一種遍歷行為。
          可迭代對象:ES6中引入了 iterable 類型,Array Set Map String arguments NodeList 都屬于 iterable,他們特點就是都擁有 [Symbol.iterator] 方法,包含他的對象被認(rèn)為是可迭代的 iterable。

          在了解這些后就知道 forEach 其實是一個迭代器,他與 for 循環(huán)本質(zhì)上的區(qū)別是 forEach 是負(fù)責(zé)遍歷(Array Set Map)可迭代對象的,而 for 循環(huán)是一種循環(huán)機制,只是能通過它遍歷出數(shù)組。

          再來聊聊究竟什么是迭代器,還記得之前提到的 Generator 生成器,當(dāng)它被調(diào)用時就會生成一個迭代器對象(Iterator Object),它有一個 .next()方法,每次調(diào)用返回一個對象{value:value,done:Boolean},value返回的是 yield 后的返回值,當(dāng) yield 結(jié)束,done 變?yōu)?true,通過不斷調(diào)用并依次的迭代訪問內(nèi)部的值。

          迭代器是一種特殊對象。ES6規(guī)范中它的標(biāo)志是返回對象的 next() 方法,迭代行為判斷在 done 之中。在不暴露內(nèi)部表示的情況下,迭代器實現(xiàn)了遍歷??创a

          let arr = [1, 2, 3, 4]  // 可迭代對象let iterator = arr[Symbol.iterator]()  // 調(diào)用 Symbol.iterator 后生成了迭代器對象console.log(iterator.next()); // {value: 1, done: false}  訪問迭代器對象的next方法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}

          我們看到了。只要是可迭代對象,調(diào)用內(nèi)部的 Symbol.iterator 都會提供一個迭代器,并根據(jù)迭代器返回的next 方法來訪問內(nèi)部,這也是 for...of 的實現(xiàn)原理。

          let arr = [1, 2, 3, 4]for (const item of arr) {    console.log(item); // 1 2 3 4 }

          把調(diào)用 next 方法返回對象的 value 值并保存在 item 中,直到 value 為 undefined 跳出循環(huán),所有可迭代對象可供for...of消費。

          再來看看其他可迭代對象:

          function num(params) {    console.log(arguments); // Arguments(6) [1, 2, 3, 4, callee: ?, Symbol(Symbol.iterator): ?]    let iterator = arguments[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: 4, done: false}    console.log(iterator.next()); // {value: undefined, done: true}}num(1, 2, 3, 4)
          let set = new Set('1234')set.forEach(item => { console.log(item); // 1 2 3 4})let iterator = set[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: 4, done: false}console.log(iterator.next()); // {value: undefined, done: true}

          所以我們也能很直觀的看到可迭代對象中的 Symbol.iterator 屬性被調(diào)用時都能生成迭代器,而 forEach 也是生成一個迭代器,在內(nèi)部的回調(diào)函數(shù)中傳遞出每個元素的值。

          (感興趣的同學(xué)可以搜下 forEach 源碼, Array Set Map 實例上都掛載著 forEach ,但網(wǎng)上的答案大多數(shù)是通過 length 判斷長度, 利用for循環(huán)機制實現(xiàn)的。但在 Set Map 上使用會報錯,所以我認(rèn)為是調(diào)用的迭代器,不斷調(diào)用 next,傳參到回調(diào)函數(shù)。

          由于網(wǎng)上沒查到答案也不妄下斷言了,有答案的人可以評論區(qū)留言)

          for循環(huán)和forEach的語法區(qū)別

          了解了本質(zhì)區(qū)別,在應(yīng)用過程中,他們到底有什么語法區(qū)別呢?

          1. forEach 的參數(shù)。
          2. forEach 的中斷。
          3. forEach 刪除自身元素,index不可被重置。
          4. for 循環(huán)可以控制循環(huán)起點。

          forEach 的參數(shù)

          我們真正了解 forEach 的完整傳參內(nèi)容嗎?它大概是這樣:

          arr.forEach((self,index,arr) =>{},this)

          self: 數(shù)組當(dāng)前遍歷的元素,默認(rèn)從左往右依次獲取數(shù)組元素。

          index: 數(shù)組當(dāng)前元素的索引,第一個元素索引為0,依次類推。

          arr: 當(dāng)前遍歷的數(shù)組。

          this: 回調(diào)函數(shù)中this指向。

          let arr = [1, 2, 3, 4];let person = {    name: '技術(shù)直男星辰'};arr.forEach(function (self, index, arr) {    console.log(`當(dāng)前元素為${self}索引為${index},屬于數(shù)組${arr}`);    console.log(this.name+='真帥');}, person)

          我們可以利用 arr 實現(xiàn)數(shù)組去重:

          let arr1 = [1, 2, 1, 3, 1];let arr2 = [];arr1.forEach(function (self, index, arr) {    arr.indexOf(self) === index ? arr2.push(self) : null;});console.log(arr2);   // [1,2,3]

          forEach 的中斷

          在js中有break return continue 對函數(shù)進(jìn)行中斷或跳出循環(huán)的操作,我們在 for循環(huán)中會用到一些中斷行為,對于優(yōu)化數(shù)組遍歷查找是很好的,但由于forEach屬于迭代器,只能按序依次遍歷完成,所以不支持上述的中斷行為。

          let arr = [1, 2, 3, 4],    i = 0,    length = arr.length;for (; i < length; i++) {    console.log(arr[i]); //1,2    if (arr[i] === 2) {        break;    };};
          arr.forEach((self,index) => { console.log(self); if (self === 2) { break; //報錯 };});
          arr.forEach((self,index) => { console.log(self); if (self === 2) { continue; //報錯 };});

          如果我一定要在 forEach 中跳出循環(huán)呢?其實是有辦法的,借助try/catch:

          try {    var arr = [1, 2, 3, 4];    arr.forEach(function (item, index) {        //跳出條件        if (item === 3) {            throw new Error("LoopTerminates");        }        //do something        console.log(item);    });} catch (e) {    if (e.message !== "LoopTerminates") throw e;};

          若遇到 return 并不會報錯,但是不會生效

          let arr = [1, 2, 3, 4];
          function find(array, num) { array.forEach((self, index) => { if (self === num) { return index; }; });};let index = find(arr, 2);// undefined

          forEach 刪除自身元素,index不可被重置

          在 forEach 中我們無法控制 index 的值,它只會無腦的自增直至大于數(shù)組的 length 跳出循環(huán)。所以也無法刪除自身進(jìn)行index重置,先看一個簡單例子:

          let arr = [1,2,3,4]arr.forEach((item, index) => {      console.log(item); // 1 2 3 4      index++;});

          index不會隨著函數(shù)體內(nèi)部對它的增減而發(fā)生變化。在實際開發(fā)中,遍歷數(shù)組同時刪除某項的操作十分常見,在使用forEach刪除時要注意。

          for 循環(huán)可以控制循環(huán)起點

          如上文提到的 forEach 的循環(huán)起點只能為0不能進(jìn)行人為干預(yù),而for循環(huán)不同:

          let arr = [1, 2, 3, 4],    i = 1,    length = arr.length;
          for (; i < length; i++) { console.log(arr[i]) // 2 3 4};

          那之前的數(shù)組遍歷并刪除滋生的操作就可以寫成。

          let arr = [1, 2, 1],    i = 0,    length = arr.length;
          for (; i < length; i++) { // 刪除數(shù)組中所有的1 if (arr[i] === 1) { arr.splice(i, 1); //重置i,否則i會跳一位 i--; };};console.log(arr); // [2]//等價于var arr1 = arr.filter(index => index !== 1);console.log(arr1) // [2]

          for循環(huán)和forEach的性能區(qū)別

          在性能對比方面我們加入一個 map 迭代器,它與 filter 一樣都是生成新數(shù)組。我們對比 for forEach map 的性能在瀏覽器環(huán)境中都是什么樣的:

          性能比較:for > forEach > map 在chrome 62 和 Node.js v9.1.0環(huán)境下:for 循環(huán)比 forEach 快1倍,forEach 比 map 快20%左右。

          原因分析for:for循環(huán)沒有額外的函數(shù)調(diào)用棧和上下文,所以它的實現(xiàn)最為簡單。forEach:對于forEach來說,它的函數(shù)簽名中包含了參數(shù)和上下文,所以性能會低于 for 循環(huán)。

          map:map 最慢的原因是因為 map 會返回一個新的數(shù)組,數(shù)組的創(chuàng)建和賦值會導(dǎo)致分配內(nèi)存空間,因此會帶來較大的性能開銷。

          如果將map嵌套在一個循環(huán)中,便會帶來更多不必要的內(nèi)存消耗。當(dāng)大家使用迭代器遍歷一個數(shù)組時,如果不需要返回一個新數(shù)組卻使用 map 是違背設(shè)計初衷的。在我前端合作開發(fā)時見過很多人只是為了遍歷數(shù)組而用 map 的:

          let data = [];let data2 = [1,2,3];data2.map(item=>data.push(item));

          寫在最后:這是面試遇到的一個問題,當(dāng)時只知道語法區(qū)別。并沒有從可迭代對象,迭代器,生成器和性能方面,多角度進(jìn)一步區(qū)分兩者的異同,也希望能把一個簡單的問題從多角度展開細(xì)講,讓大家正在搞懂搞透徹。

          本文完~


          學(xué)習(xí)更多技能

          請點擊下方公眾號

          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码人妻精品一区二区三区99仓 | 大香蕉98 | 青娱乐亚| 91视频观看 | 久干网|