區(qū)別:1、forEach是一個迭代器,是負責遍歷(Array Set Map)可迭代對象的;而for是一種循環(huán)機制,只是能通過它遍歷出數(shù)組。2、for循環(huán)中會用到一些中斷行為,對于優(yōu)化數(shù)組遍歷查找是很好的,但由于forEach屬于迭代器,只能按序依次遍歷完成,所以不支持中斷行為。3、forEach的循環(huán)起點只能為0,且不能進行人為干預;而for循環(huán)不同,可以人為控制循環(huán)起點。
前端(vue)入門到精通課程:進入學習
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API調(diào)試工具:點擊使用
本教程操作環(huán)境:windows7系統(tǒng)、ECMAScript 6版、Dell G3電腦。
for循環(huán)和forEach本質(zhì)區(qū)別
for
循環(huán)是js提出時就有的循環(huán)方法。
forEach
是ES5提出的,掛載在可迭代對象原型上的方法,例如Array
Set
Map
。
forEach
是一個迭代器,負責遍歷可迭代對象。
那么遍歷,迭代,可迭代對象分別是什么呢。
-
遍歷:指的對數(shù)據(jù)結(jié)構(gòu)的每一個成員進行有規(guī)律的且為一次訪問的行為。
-
迭代:迭代是遞歸的一種特殊形式,是迭代器提供的一種方法,默認情況下是按照一定順序逐個訪問數(shù)據(jù)結(jié)構(gòu)成員。迭代也是一種遍歷行為。
-
可迭代對象:ES6中引入了
iterable
類型,Array
Set
Map
String
arguments
NodeList
都屬于iterable
,他們特點就是都擁有[Symbol.iterator]
方法,包含他的對象被認為是可迭代的iterable
。
forEach
其實是一個迭代器,他與 for
循環(huán)本質(zhì)上的區(qū)別是 forEach
是負責遍歷(Array
Set
Map
)可迭代對象的,而 for
循環(huán)是一種循環(huán)機制,只是能通過它遍歷出數(shù)組。
什么是迭代器,當它被調(diào)用時就會生成一個迭代器對象(Iterator Object),它有一個 .next()
方法,每次調(diào)用返回一個對象{value:value,done:Boolean}
,value
返回的是 yield
后的返回值,當 yield
結(jié)束,done
變?yōu)?true
,通過不斷調(diào)用并依次的迭代訪問內(nèi)部的值。
迭代器是一種特殊對象。ES6規(guī)范中它的標志是返回對象的 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ù)中傳遞出每個元素的值
for
循環(huán)和forEach
的語法區(qū)別
了解了本質(zhì)區(qū)別,在應(yīng)用過程中,他們到底有什么語法區(qū)別呢?
-
forEach
的參數(shù)。 -
forEach
的中斷。 -
forEach
刪除自身元素,index不可被重置。 -
for
循環(huán)可以控制循環(huán)起點。
forEach
的參數(shù)
我們真正了解 forEach
的完整傳參內(nèi)容嗎?它大概是這樣:
arr.forEach((self,index,arr) =>{},this)
-
self: 數(shù)組當前遍歷的元素,默認從左往右依次獲取數(shù)組元素。
-
index: 數(shù)組當前元素的索引,第一個元素索引為0,依次類推。
-
arr: 當前遍歷的數(shù)組。
-
this: 回調(diào)函數(shù)中this指向。
let arr = [1, 2, 3, 4]; arr.forEach(function (self, index, arr) { console.log(`當前元素為${self}索引為${index},屬于數(shù)組${arr}`); }, 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ù)進行中斷或跳出循環(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)。所以也無法刪除自身進行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不能進行人為干預,而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)建和賦值會導致分配內(nèi)存空間,因此會帶來較大的性能開銷。
如果將map
嵌套在一個循環(huán)中,便會帶來