抵達戰場的5方勢力
自我介紹環節
#for
我是遍歷界最早出現的一方諸侯,在座的各位需稱我一聲爺爺。我能滿足開發人員的絕大多數的需求。
分別是for
, foreach
, map
, for...in
, for.. .of
// 遍历数组 let arr = [1,2,3]; for(let i = 0;i < arr.length;i++){ console.log(i) // 索引,数组下标 console.log(arr[i]) // 数组下标所对应的元素 } // 遍历对象 let profile = {name:"April",nickname:"二十七刻",country:"China"}; for(let i = 0, keys=Object.keys(profile); i < keys.length;i++){ console.log(keys[i]) // 对象的键值 console.log(profile[keys[i]]) // 对象的键对应的值 } // 遍历字符串 let str = "abcdef"; for(let i = 0;i < str.length ;i++){ console.log(i) // 索引 字符串的下标 console.log(str[i]) // 字符串下标所对应的元素 } // 遍历DOM 节点 let articleParagraphs = document.querySelectorAll('.article > p'); for(let i = 0;i<articleParagraphs.length;i++){ articleParagraphs[i].classList.add("paragraph"); // 给class名为“article”节点下的 p 标签添加一个名为“paragraph” class属性。 }
forEach
我是ES5版本發布的。以升序為數組中含有效值的每一項執行一次 callback 函數,那些已刪除或未初始化的項將被跳過(例如在稀疏數組上)。我是 for 迴圈的加強版。
// 遍历数组 let arr = [1,2,3]; arr.forEach(i => console.log(i)) // logs 1 // logs 2 // logs 3 // 直接输出了数组的元素 //遍历对象 let profile = {name:"April",nickname:"二十七刻",country:"China"}; let keys = Object.keys(profile); keys.forEach(i => { console.log(i) // 对象的键值 console.log(profile[i]) // 对象的键对应的值 })
map
我也是ES5版本發布的,我可以建立一個新數組,新數組的結果是原始數組中的每個元素都呼叫一次提供的函數後的回傳值。
let arr = [1,2,3,4,5]; let res = arr.map(i => i * i); console.log(res) // logs [1, 4, 9, 16, 25]
for...in枚舉
我是ES5版本發布的。以任意順序遍歷一個物件的除Symbol以外的可枚舉屬性。
// 遍历对象 let profile = {name:"April",nickname:"二十七刻",country:"China"}; for(let i in profile){ let item = profile[i]; console.log(item) // 对象的键值 console.log(i) // 对象的键对应的值 // 遍历数组 let arr = ['a','b','c']; for(let i in arr){ let item = arr[i]; console.log(item) // 数组下标所对应的元素 console.log(i) // 索引,数组下标 // 遍历字符串 let str = "abcd" for(let i in str){ let item = str[i]; console.log(item) // 字符串下标所对应的元素 console.log(i) // 索引 字符串的下标 }
for...of迭代
我是ES6版本發布的。在可迭代物件(包括 Array,Map,Set,String,TypedArray,arguments 物件等等)上建立一個迭代循環,呼叫自訂迭代鉤子,並為每個不同屬性的值執行語句。
// 迭代数组数组 let arr = ['a','b','c']; for(let item of arr){ console.log(item) } // logs 'a' // logs 'b' // logs 'c' // 迭代字符串 let str = "abc"; for (let value of str) { console.log(value); } // logs 'a' // logs 'b' // logs 'c' // 迭代map let iterable = new Map([["a", 1], ["b", 2], ["c", 3]] for (let entry of iterable) { console.log(entry); } // logs ["a", 1] // logs ["b", 2] // logs ["c", 3] // 迭代map获取键值 for (let [key, value] of iterable) { console.log(key) console.log(value); } // 迭代set let iterable = new Set([1, 1, 2, 2, 3, 3,4]); for (let value of iterable) { console.log(value); } // logs 1 // logs 2 // logs 3 // logs 4 // 迭代 DOM 节点 let articleParagraphs = document.querySelectorAll('.article > p'); for (let paragraph of articleParagraphs) { paragraph.classList.add("paragraph"); // 给class名为“article”节点下的 p 标签添加一个名为“paragraph” class属性。 } // 迭代arguments类数组对象 (function() { for (let argument of arguments) { console.log(argument); } })(1, 2, 3); // logs: // 1 // 2 // 3 // 迭代类型数组 let typeArr = new Uint8Array([0x00, 0xff]); for (let value of typeArr) { console.log(value); } // logs: // 0 // 255
經過第一輪的自我介紹和技能展示後,我們了解到:
#for語句是最原始的循環語句。定義一個變數i(數字型,表示陣列的下標),依照一定的條件,對i進行迴圈累加。條件通常為循環物件的長度,當超過長度就停止循環。因為物件無法判斷長度,所以搭配Object.keys()使用。
forEach ES5 提出。自稱是for語句的加強版,可以發現它比for語句在寫法上簡單了許多。但是本質上也是數組的循環。 forEach每個陣列元素執行一次 callback 函數。也就是呼叫它的數組,因此,不會改變原數組。傳回值是undefine。
map ES5 提出。給原數組中的每個元素都依序呼叫一次 callback 函數。產生一個新數組,不修改呼叫它的原始數組本身。傳回值是新的數組。
for...in ES5 提出。遍歷物件上的可枚舉屬性,包括原型物件上的屬性,且依任意順序進行遍歷,也就是順序不固定。遍歷數組時把數組的下標當作鍵值,此時的i是個字串型的。它是為遍歷物件屬性而建構的,不建議與陣列一起使用。
for...of ES6 提出。只遍歷可迭代物件的資料。
能力甄別
#身為程式設計師,僅僅認識他們是遠遠不夠的,在實際開發中鑑別他們各自的優缺點。因地制宜的使用他們,揚長避短。從而提高程式的整體效能才是能力之所在。
關於跳出循環體
在迴圈中滿足一定條件就跳出循環體,或跳過不符合條件的資料繼續循環其它資料。是經常會遇到的需求。常用的語句是break 與 continue。
簡單的說一下二者的差別,就當複習好了。
break語句是跳出目前循環,並執行目前循環之後的語句;
continue語句是終止目前循環,並繼續執行下一次循環;
注意:forEach 與map 是不支援跳出循環體的,其它三種方法均支援。
原理 :查看forEach實作原理,就會理解這個問題。
Array.prototype.forEach(callbackfn [,thisArg]{ }
傳入的function是這裡的回呼函數。在回調函數裡面使用break肯定是非法的,因為break只能用來跳出循環,回調函數不是循環體。
在回呼函數中使用return,只是將結果回到上級函數,也就是這個for迴圈中,並沒有結束for迴圈,所以return也是無效的。
map() 同理。
map()鍊式呼叫
map() 方法是可以鍊式呼叫的,這意味著它可以方便的結合其它方法一起使用。例如:reduce(), sort(), filter() 等。但是其它方法並不能做到這一點。 forEach()的回傳值是undefined,所以無法鍊式呼叫。
// 将元素乘以本身,再进行求和。 let arr = [1, 2, 3, 4, 5]; let res1 = arr.map(item => item * item).reduce((total, value) => total + value); console.log(res1) // logs 55 undefined"
for...in會遍歷出原型物件上的屬性
Object.prototype.objCustom = function() {}; Array.prototype.arrCustom = function() {}; var arr = ['a', 'b', 'c']; arr.foo = 'hello for (var i in arr) { console.log(i); } // logs // 0 // 1 // 2 // foo // arrCustom // objCustom
然而在實際的開發中,我們並不需要原型物件上的屬性。這種情況下我們可以使用hasOwnProperty() 方法,它會回傳一個布林值,指示物件本身屬性中是否具有指定的屬性(也就是,是否有指定的鍵)。如下:
Object.prototype.objCustom = function() {}; Array.prototype.arrCustom = function() {}; var arr = ['a', 'b', 'c']; arr.foo = 'hello for (var i in arr) { if (arr.hasOwnProperty(i)) { console.log(i); } } // logs // 0 // 1 // 2 // foo // 可见数组本身的属性还是无法摆脱。此时建议使用 forEach
对于纯对象的遍历,选择for..in枚举更方便;对于数组遍历,如果不需要知道索引for..of迭代更合适,因为还可以中断;如果需要知道索引,则forEach()更合适;对于其他字符串,类数组,类型数组的迭代,for..of更占上风更胜一筹。但是注意低版本浏览器的是配性。
性能
有兴趣的读者可以找一组数据自行测试,文章就直接给出结果了,并做相应的解释。
for > for-of > forEach > map > for-in
for 循环当然是最简单的,因为它没有任何额外的函数调用栈和上下文;
for...of只要具有Iterator接口的数据结构,都可以使用它迭代成员。它直接读取的是键值。
forEach,因为它其实比我们想象得要复杂一些,它实际上是array.forEach(function(currentValue, index, arr), thisValue)它不是普通的 for 循环的语法糖,还有诸多参数和上下文需要在执行的时候考虑进来,这里可能拖慢性能;
map() 最慢,因为它的返回值是一个等长的全新的数组,数组创建和赋值产生的性能开销很大。
for...in需要穷举对象的所有属性,包括自定义的添加的属性也能遍历到。且for...in的key是String类型,有转换过程,开销比较大。
总结
在实际开发中我们要结合语义话、可读性和程序性能,去选择究竟使用哪种方案。
如果你需要将数组按照某种规则映射为另一个数组,就应该用 map。
如果你需要进行简单的遍历,用 forEach 或者 for of。
如果你需要对迭代器进行遍历,用 for of。
如果你需要过滤出符合条件的项,用 filterr。
如果你需要先按照规则映射为新数组,再根据条件过滤,那就用一个 map 加一个 filter。
总之,因地制宜,因时而变。千万不要因为过分追求性能,而忽略了语义和可读性。在实际开发中,让他们扬长避短,优势互补,让程序趋近最优才是我们要做的。
推荐教程:《JS教程》
以上是為了效能選擇for迴圈遍歷嗎?的詳細內容。更多資訊請關注PHP中文網其他相關文章!