摘要:中可以實現遍歷的數據類型主要是對象,其中包括普通對象與數組。遍歷器是一種接口,為各種不同的數據結構提供統一的訪問機制。實例五遍歷器對象實例五是的遍歷過程,通過手動調用其對象的方法實現信息獲取。為每個數組元素執行函數。
前言
??將依據自身痛點學習,計劃對原生JavaScript寫一個系統,本文為第一篇,感興趣的同學可以關注個人公眾號:ZeroToOneMe,或者github博客,將持續輸出。
??JavaScript中可以實現遍歷的數據類型主要是對象,其中包括普通對象與數組。實現遍歷的方式有很多,本文梳理下JavaScript中能實現遍歷的方式。
對象&&數組 for...in??語句以任意順序遍歷一個對象自有的、繼承的、可枚舉的、非Symbol的屬性。對于每個不同的屬性,語句都會被執行。
語法:
for (variable in object) { xxx }
參數:
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遍歷,可能會存在以下幾個問題:
得到的index索引值為字符串,不能直接做幾何運算,需要先做數據類型轉換處理;
可能以任意順序做遍歷的,即遍歷順序可能不是按照數組內部順序;
會遍歷數組所有可枚舉的屬性,包括原型。
??在一定程度上來看,使用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 }for...of
??語句在可迭代對象(包括 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循環的所有缺陷。
對象 Object.keys()??返回一個由一個給定對象的自身可枚舉屬性組成的數組,數組中屬性名的排列順序和使用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循環
??用于創建一個循環,它包含了三個可選的表達式,三個可選的表達式包圍在圓括號中并由分號分隔, 后跟一個在循環中執行的語句(通常是一個塊語句)。
語法:
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 }forEach
??按升序為數組中含有效值的每一項執行一次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()),之后的元素將被跳過。
filter??“過濾”,為數組中的每個元素調用一次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]map
??創建一個新數組,其結果是該數組中的每個元素都調用一個提供的函數后返回的結果。其基本用法與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沒有變reduce
??對累加器和數組中的每個元素(從左到右)應用一個函數,最終合并為一個值。
語法:
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等方法,感興趣的同學可以自行了解。
??筆者現在還是一個前端新人,對遍歷的實現方式不太清楚,借此梳理的機會,熟悉相關的實現,文章如有不正確的地方歡迎各位大佬指正,也希望有幸看到文章的同學也有收獲,一起成長!
——本文首發于個人公眾號———
最后,歡迎大家關注我的公眾號,一起學習交流。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104256.html
摘要:接觸這么多年,第一次總結一下它的遍歷語法。而且你必須借助特定的結構才能遍歷數據結構。它的作用是遍歷對象的鍵名。建議僅在遍歷數組的時候使用。另一個優點是,它可以遍歷任何部署了接口的數據結構,甚至是非的數據類型,即自己定義的數據結構。 接觸JavaScript這么多年,第一次總結一下它的遍歷語法。以前我大部分時間都在老版本的JavaScript下寫代碼,所以大部分時間都是用for...in...
摘要:日常開發中我們難免需要對數組和對象進行遍歷,今天抽空來總結下遍歷數組和對象常用的方法。使用遍歷對象注只能遍歷出自身可枚舉的屬性,而不能遍歷出原型鏈上面的屬性。 日常開發中我們難免需要對數組和對象進行遍歷,今天抽空來總結下遍歷數組和對象常用的方法。 Javascript遍歷數組總結 我們定義一個數組 var arr = [2,4,6]; 1.使用for循環遍歷 var lengt...
摘要:所以說遍歷屬性時,要考慮這兩個因素。開始遍歷先定義兩個類吧和,后者繼承前者。然后再聲明并初始化一個的實例。 原文鏈接 JavaScript 中遍歷對象的屬性 參考 JavaScript中的屬性:如何遍歷屬性《JavaScript 高級程序設計》 概述 遍歷 JavaScript 對象中的屬性沒有其他語言那么簡單,因為兩個因素會影響屬性的遍歷:對象屬性的屬性描述符 (property ...
摘要:遍歷對象的屬性并沒有像中遍歷一個那么簡單,主要原因有以下兩點在中對象的屬性分為可枚舉與不可枚舉之分他們是由屬性的的值決定的。 javascript遍歷對象的屬性并沒有像java中遍歷一個Map那么簡單,主要原因有以下兩點: 在javascript中對象的屬性分為可枚舉與不可枚舉之分,他們是由屬性的 enumerable 的值決定的。可枚舉性 決定了這個屬性是否可以被 for-in ...
摘要:缺陷是不能使用,但可以用來,適用于鏈式場景,如,適用于全部元素的遍歷,缺陷是不知道迭代器,新特性,大家可以慢慢玩 原文鏈接 《JavaScript 數組遍歷》 參考 For-each over an array in JavaScript?Why is for(var item in list) with arrays considered bad practice in JavaSc...
摘要:在中,引入了新的循環,即循環。而且,遍歷數組元素的順序可能是隨機的。所以,鑒于以上種種缺陷,我們需要改進原先的循環。總結一下,循環有以下幾個特征這是最簡潔最直接的遍歷數組元素的語法。 在ECMAScript5(簡稱 ES5)中,有三種 for 循環,分別是: 簡單for循環 for-in forEach 在2015年6月份發布的ECMAScript6(簡稱 ES6)中,新增了一種循...
閱讀 1040·2021-09-13 10:29
閱讀 3391·2019-08-29 18:31
閱讀 2633·2019-08-29 11:15
閱讀 3012·2019-08-26 13:25
閱讀 1369·2019-08-26 12:00
閱讀 2293·2019-08-26 11:41
閱讀 3377·2019-08-26 10:31
閱讀 1488·2019-08-26 10:25