將依據自身痛點學習,計劃對原生JavaScript寫一個系統,本文爲第一篇,感興趣的同窗能夠關注我的公衆號:ZeroToOneMe,或者github博客,將持續輸出。前端
JavaScript
中能夠實現遍歷的數據類型主要是對象,其中包括普通對象與數組。實現遍歷的方式有不少,本文梳理下JavaScript
中能實現遍歷的方式。java
語句以任意順序遍歷一個對象自有的、繼承的、可枚舉的、非Symbol
的屬性。對於每一個不一樣的屬性,語句都會被執行。git
語法:github
for (variable in object) {
xxx
}
複製代碼
參數:express
variable:在每次迭代時,將不一樣的屬性名分配給變量。
object:被迭代枚舉其屬性的對象。編程
從定義上來看,for...in
語句遍歷對象和組數都是能夠的,可是此方式使用在數組可能會帶來一些咱們並不想看到的東西,看下面的實例一:數組
// 實例一
Array.prototype.name = 'fe'
let arr = [10, 20, 30, 40, 50]
arr.str = 'hello'
for(let index in arr) {
console.log(index) // 0, 1, 2, 3, 4, str, name
}
複製代碼
上面的實例一輸出結果能夠看出,直接獲得不是數組元素值,而是數組的索引值(鍵名),同時還有其自定義屬性以及其原型鏈上的屬性和方法‘str’,‘name’兩項,數組自己也是對象。緩存
即便使用getOwnPropertyNames()
或執行hasOwnProperty()
來肯定某屬性是不是對象自己的屬性能夠避免原型鏈上的屬性和方法不輸出,但仍是不能避免自定義的屬性輸出。實例二:數據結構
// 實例二
Array.prototype.name = 'fe'
let arr = [10, 20, 30, 40, 50]
arr.str = 'hello'
for(let index in arr) {
if (arr.hasOwnProperty(index)) {
console.log(index) // 0, 1, 2, 3, 4, str
}
}
複製代碼
數組使用for...in
遍歷,可能會存在如下幾個問題:閉包
在必定程度上來看,使用for...in
遍歷數組是一個很糟糕的選擇,不推薦使用for...in
語句遍歷數組,對開發經驗欠缺的新人不友好,比較容易踩到坑,會遇到不少意想不到的問題。遍歷數組更多的推薦是for...of
與foreach
這兩種方式,下面也會詳細的梳理這兩種方式。
注意點:
for...in
比較適合遍歷普通對象,遍歷獲得的結果是對象的鍵名,其順序也是無序的,無關順序。也能夠經過break
或者return false
中斷遍歷。- 在迭代過程當中最好不要在對象上進行添加、修改或者刪除屬性的操做,除非是對當前正在被訪問的屬性。
// 實例三
let obj = {
name: 'fe',
age: 18
}
for(let key in obj) {
console.log(key) // name age
}
複製代碼
語句在可迭代對象(包括 Array
,Map
,Set
,String
,TypedArray
,arguments
對象,NodeList
對象等)上建立一個迭代循環,調用自定義迭代鉤子,併爲每一個不一樣屬性的值執行語句。該語句是ES6新增遍歷方式,功能仍是比較強大的。
語法:
for (variable of iterable) {
xxx
}
複製代碼
參數:
variable:在每次迭代中,將不一樣屬性的值分配給變量。
iterable:一個具備可枚舉屬性而且能夠迭代的對象。
// 實例四
let arr = [10, 20, 30, 40, 50]
arr.str = 'hello'
for(let value of arr) {
console.log(value) // 10 20 30 40 50
}
複製代碼
for...of
語句經常使用在遍歷數組,從實例四看出,for...of
能夠直接獲得數組索引中的值,可是不會將其實例屬性的值返回。緣由在於for...of
循環調用遍歷器接口,數組的遍歷器接口只返回具備數字索引的屬性,因此for...of
循環不會返回數組arr
的str
屬性。
話說咱們平時的開發中經常使用for..of
遍歷數組,從上面的定義來看,for...of
不只僅能遍歷數組,也能夠遍歷String
(字符串)、Map
、Set
等數據結構,是由於這幾個數據結構默認內置了遍歷器接口,Iterator接口
,即Symbol.iterator
方法。
也就是說有了遍歷器接口,數據結構就能夠用for...of
循環遍歷。遍歷器(Iterator
)是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator 接口
,就能夠完成遍歷操做。Iterator接口
與for...of
都是ES6提出的特性,Iterator接口
也就主要供for...of
消費。
// 實例五
let arr = [10, 20, 30, 40, 50]
arr.str = 'hello'
let item = arr[Symbol.iterator]() // 遍歷器對象
console.log(item.next()) // { value: 10, done: false }
console.log(item.next()) // { value: 20, done: false }
console.log(item.next()) // { value: 30, done: false }
console.log(item.next()) // { value: 40, done: false }
console.log(item.next()) // { value: 50, done: false }
console.log(item.next()) // { value: undefined, done: true }
複製代碼
實例五是Iterator
的遍歷過程,經過手動調用其對象的next()
方法實現信息獲取。默認的Iterator接口
部署在數據結構的Symbol.iterator
屬性。對於原生部署Iterator接口
的數據結構,不用本身寫遍歷器生成函數,for...of
循環會自動遍歷。能夠參考實例四。
// 實例六
let str = 'abc'
let item = str[Symbol.iterator]()
console.log(item.next()) // { value: 'a', done: false }
console.log(item.next()) // { value: 'b', done: false }
console.log(item.next()) // { value: 'c', done: false }
console.log(item.next()) // { value: undefined, done: true }
for (let item of str) {
console.log(item) // a b c
}
複製代碼
實例六爲字符串的遍歷演示,還有其餘幾種數據結構(Map
,Set
,TypedArray
,arguments
對象,NodeList
對象)原生部署了Iterator接口
,能夠直接使用for...of
完成遍歷,在此不在提供代碼實例,能夠自行測試。
for..of
不能遍歷對象,由於對象默認沒有部署Iterator接口
,沒 有默認部署Iterator接口
緣由在於對象屬性是無序的,哪一個屬性先遍歷,哪一個屬性後遍歷沒法肯定。本質上,遍歷器是一種線性處理,對於任何非線性的數據結構,部署遍歷器接口,就等於部署一種線性轉換。也能夠手動爲對象部署Iterator接口
,看下面的實例七。
// 實例七
let obj = {
data: [ 'hello', 'world' ],
[Symbol.iterator]() {
const self = this
let index = 0
return {
next() {
if (index < self.data.length) {
return {
value: self.data[index++],
done: false
};
} else {
return { value: undefined, done: true }
}
}
};
}
}
for (let item of obj) {
console.log(item) // hello world
}
複製代碼
總結下for..of
的幾個特徵:
Iterator接口
的數據結構;for-in
循環的全部缺陷。 返回一個由一個給定對象的自身可枚舉屬性組成的數組,數組中屬性名的排列順序和使用for...in
循環遍歷該對象時返回的順序一致。
語法:
Object.keys(obj)
複製代碼
參數:
obj:要返回其枚舉自身屬性的對象
返回一個全部元素爲字符串的數組,其元素來自於從給定的object
上面可直接枚舉的屬性。這些屬性的順序與手動遍歷該對象屬性時的一致。
// 實例八
let obj = {
name: 'fe',
age: 18
}
let result = Object.keys(obj)
console.log(result) // [ 'name', 'age' ]
複製代碼
注意點:
- 在ES5裏,若是此方法的參數不是對象(而是一個原始值),那麼它會拋出
TypeError
。在ES6中,非對象的參數將被強制轉換爲一個對象。
consoloe.log(Object.keys('foo')) // TypeError: "foo" is not an object (ES5)
consoloe.log(Object.keys('foo')) // ["0", "1", "2"] (ES6)
複製代碼
對象也能夠經過和Object.keys(obj)
搭配,使用for...of
來遍歷普通對象的屬性。
// 實例九
let obj = {
name: 'fe',
age: 18
}
for(let key of Object.keys(obj)) {
console.log(obj[key]) // fe 18
}
複製代碼
用於建立一個循環,它包含了三個可選的表達式,三個可選的表達式包圍在圓括號中並由分號分隔, 後跟一個在循環中執行的語句(一般是一個塊語句)。
語法:
for ([initialization]; [condition]; [final-expression]) {
statement
}
複製代碼
參數:
- initialization:一個表達式 (包含賦值語句) 或者變量聲明;
- condition:一個條件表達式被用於肯定每一次循環是否能被執行;
- final-expression:每次循環的最後都要執行的表達式;
- statement:只要condition的結果爲true就會被執行的語句。
`
// 實例十
let arr = [1, 2, 3]
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]) // 1 2 3
}
複製代碼
for循環
爲編程語言中最原始的一種遍歷方式,涵蓋了幾乎全部的編程語言,在JavaScript中其只能遍歷數組,不能遍歷對象。實例十是for循環
最常規的寫法。咱們不注意的話,也比較容易帶來本能夠避免的幾個「問題」,咱們須要注意:
在for循環
中使用var
或者未聲明的變量,變量做用於全局,會來沒必要要的麻煩,使用let
聲明變量生成塊級做用域,或者造成閉包;// 實例十一
let arr = [1, 2, 3]
for (let i = 0, len = arr.length; i < len; i++) {
console.log(arr[i]) // 1 2 3
}
複製代碼
按升序爲數組中含有效值的每一項執行一次callback
函數,那些已刪除或者未初始化的項將被跳過。
語法:
array.forEach(function(currentValue, index, arr), thisValue) 複製代碼
參數:
- currentValue(必選):數組當前項的值
- index(可選):數組當前項的索引
- arr(可選):數組對象自己
- thisValue(可選):當執行回調函數時用做
this
的值(參考對象),默認值爲undefined
注意點:
- 沒有辦法停止或者跳出
forEach
循環,除了拋出一個異常。- 只能用
return
退出本次回調,進行下一次回調,並老是返回undefined
值,即便你return
了一個值。forEach
被調用時,不直接改變調用它的對象,可是對象可能會被callback
改變。
常見規則(一樣適用於filter
、map
方法):
callback
前就會肯定。callback
函數。callback
訪問到。callback
的值是遍歷到他們那一刻的值。// 實例十二
let arr = [1, 2, 3, , 5] // 倒數第二個元素爲空,不會遍歷
let result = arr.forEach((item, index, array) => {
arr[0] = '修改元素值'
console.log(item) // 1 2 3 5
})
console.log(arr) // ["修改元素值", 2, 3, , 5]
複製代碼
// 實例十三
let arr = [1, 2, 3, , 5] // 倒數第二個元素爲空,不會遍歷
let result = arr.forEach((item, index, array) => {
arr[1] = '修改元素值'
console.log(item) // 1 修改元素值 3 5
})
console.log(arr) // [1, '修改元素值', 3, , 5]
複製代碼
實例十二和實例十三都對數組元素值作了修改,然而實例十二中item
值沒有變化,實例十三中item
值變化了,爲啥???
實例十二在執行forEach
時,修改了arr[0]
的值,數組arr
的值發生了改變,而後控制檯輸出item
的值沒有變,緣由是僅僅是修改了arr[0]
的值,item
的值沒有變化,控制檯輸出的item
的值依舊仍是以前forEach
保存的item
的值。
實例十三在執行forEach
時,修改了arr[1]
的值,控制檯輸出item
與數組arr
的值均發生變化,在第一次遍歷的時候,數組arr
的第二個元素值已經變化了,在第二次遍歷的時候,forEach
回調函數的參數值發生了變化,即控制檯輸出item
值發生變化。
若是已經存在的值被改變,則傳遞給callback
的值是forEach
遍歷到他們那一刻的值。
// 實例十四
let arr = [1, 2, 3, , 5] // 倒數第二個元素爲空,不會遍歷
let result = arr.forEach((item, index, array) => {
arr.push('添加到尾端,不會被遍歷') // 調用 forEach 後添加到數組中的項不會被 callback 訪問到
console.log(item) // 1 2 3 5
return item // return只能結束本次回調 會執行下次回調
console.log('不會執行,由於return 會執行下一次循環回調')
})
console.log(result) // 即便return了一個值,也仍是返回undefined
複製代碼
// 實例十五
let arr = [1, 2, 3, , 5] // 倒數第二個元素爲空,不會遍歷
let result = arr.forEach((item, index, array) => {
if (item === 2) {
arr.shift()
}
console.log(item) // 1 2 5
})
複製代碼
已刪除的項不會被遍歷到。若是已訪問的元素在迭代時被刪除了(例如使用 shift()
),以後的元素將被跳過。
「過濾」,爲數組中的每一個元素調用一次callback
函數,並利用全部使得callback
返回true
或等價於true
的值的元素建立一個新數組。
語法:
let newArray = arr.filter(function(currentValue, index, arr), thisValue) 複製代碼
參數:
- currentValue(必選):數組當前項的值;
- index(可選):數組當前項的索引;
- arr(可選):數組對象自己;
- thisValue(可選):當執行回調函數時用做
this
的值(參考對象),默認值爲undefined
。
注意點:
- 不修改調用它的原數組自己,固然在
callback
執行時改變原數組另說;callback
函數返回值不必定非要是Boolean
值,只要是弱等於== true/false
也沒問題。
// 實例十六
let arr = [1, 2, 3, , 5] // 倒數第二個元素爲空,不會遍歷
let result = arr.filter(function(item, index, array) {
arr.push(6) // 在調用 filter 以後被添加到數組中的元素不會被 filter 遍歷到
arr[1] = 4 // 若是已經存在的元素被改變了,則他們傳入 callback 的值是 filter 遍歷到它們那一刻的值
console.log(item) // 1 4 3 5
return item < 5 // 返回數組arr中小於5的元素
})
console.log(result) // [1, 4, 3]
複製代碼
// 實例十七
let arr = [0, 1, 2, 3, 4]
let result = arr.filter(function(item, index, array) {
return item // 作類型轉換
})
console.log(result) // [1, 2, 3, 4]
複製代碼
建立一個新數組,其結果是該數組中的每一個元素都調用一個提供的函數後返回的結果。其基本用法與forEach
相似,不一樣在於map的callback
須要有return
值。
語法:
let newArray = arr.map(function(currentValue, index, arr), thisValue) 複製代碼
參數:
- currentValue(必選):數組當前項的值
- index(可選):數組當前項的索引
- arr(可選):數組對象自己
- thisValue(可選):當執行回調函數時用做
this
的值(參考對象),默認值爲undefined
注意點:
- 不修改調用它的原數組自己,固然在
callback
執行時改變原數組另說。callback
函數只會在有值的索引上被調用;那些歷來沒被賦過值或者使用delete
刪除的索引則不會被調用。
map
方法的主要做用實際上是對原數組映射產生新數組,看實例十八:
// 實例十八
let arr = [1, 2, 3]
let result = arr.map(function(item, index, array) {
return item * 2
})
console.log(result) // [2, 4, 6]
console.log(arr) // [1, 2, 3] // 原數組arr沒有變
複製代碼
對累加器和數組中的每一個元素(從左到右)應用一個函數,最終合併爲一個值。
語法:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue) 複製代碼
參數:
- total(必選):累計器累計回調的返回值; 它是上一次調用回調時返回的累積值,或initialValue
- currentValue(必選):數組中正在處理的元素
- currentIndex(可選):數組中正在處理的當前元素的索引 若是提供了initialValue,則起始索引號爲
0
,不然爲1
- arr(可選):調用
reduce()
的數組- initialValue(可選):做爲第一次調用
callback
函數時的第一個參數的值。 若是沒有提供初始值,則將使用數組中的第一個元素。 在沒有初始值的空數組上調用reduce
將報錯
注意點:
- 若是沒有提供initialValue,reduce 會從索引1的地方開始執行 callback 方法,跳過第一個索引。若是提供initialValue,從索引0開始。
回調函數第一次執行時:
reduce()
時提供了initialValue
,total
取值爲initialValue
,currentValue
取數組中的第一個值;initialValue
,那麼total
取數組中的第一個值,currentValue
取數組中的第二個值;initialValue
,會拋出TypeError
;initialValue
, 或者有提供initialValue
可是數組爲空,那麼此惟一值將被返回而且callback
不會被執行。// 實例十九
let sum = [1, 2, 3].reduce(function (total, currentValue, currentIndex) {
return total + currentValue
})
console.log(sum) // 6
複製代碼
本文梳理了對象和數組幾種比較經常使用的遍歷方式。數組的方法有不少,有部份內容本文沒有涉及到,例如some
,every
,reduceRight
,find
,findIndex
等方法,感興趣的同窗能夠自行了解。
筆者如今仍是一個前端新人,對遍歷的實現方式不太清楚,藉此梳理的機會,熟悉相關的實現,文章若有不正確的地方歡迎各位大佬指正,也但願有幸看到文章的同窗也有收穫,一塊兒成長!
——本文首發於我的公衆號———