|
几种遍历方法中for执行最快,它没有任何额外的函数调用栈和上下文。但在实际开发中我们要结合语义话、可读性和程序性能,去选择究竟使用哪种方案。下面来看for , foreach , map , for...in , for...of五种方法现场battle。 自我介绍& _* q6 ]( k; h& P% y; o : C# [* B' k* R for" B: _; P7 p! ^. N. X 我是最早出现的一方遍历语句,在座的各位需称我一声爷爷。我能满足开发人员的绝大多数的需求。. n, r( j c6 ?9 B) C3 A/ O( z // 遍历数组8 Z% \4 ^. Q: V- G6 f' b2 `9 n let arr = [1,2,3]; for(let i = 0;i < arr.length;i++){ console.log(i) // 索引,数组下标 console.log(arr) // 数组下标所对应的元素 }( i, [, ^$ i5 j9 E // 遍历对象- F/ O! S' I) P Z Z8 Z& _) V% [ let profile = {name:"April",nickname:"二十七刻",country:"China"}; for(let i = 0, keys=Object.keys(profile); i < keys.length;i++){( a# }% X- ~2 M4 n3 Q4 ?) G( a console.log(keys) // 对象的键值& u5 Z" V4 [+ B; ^6 O: J$ k console.log(profile[keys]) // 对象的键对应的值 } ; S& k1 J3 I; J0 d // 遍历字符串# R6 J: a$ H6 {) r5 \+ U' F, q" A let str = "abcdef";3 V3 Z3 I4 w2 `( l2 A! A for(let i = 0;i < str.length ;i++){5 G3 {4 f. @9 }; X- R5 { console.log(i) // 索引 字符串的下标 console.log(str) // 字符串下标所对应的元素* \3 @3 c3 W) ?0 s: K }- |4 o9 o& N" x% C // 遍历DOM 节点 let articleParagraphs = document.querySelectorAll('.article > p'); for(let i = 0;i<articleParagraphs.length;i++){: R. I: [# C( [% @ articleParagraphs.classList.add("paragraph"); // 给class名为“article”节点下的 p 标签添加一个名为“paragraph” class属性。. O x# H9 P9 {2 n0 X- _' y }, z1 L& n1 Z0 [ forEach : O; |1 o/ L3 w P0 }* h- N 我是ES5版本发布的。按升序为数组中含有效值的每一项执行一次 callback 函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。我是 for 循环的加强版。 W, _7 `% h7 Z+ |% o1 A // 遍历数组 {1 B4 b) r6 r$ w1 p let arr = [1,2,3]; arr.forEach(i => console.log(i))1 |. w4 E O. @9 y8 @% ? 2 o1 x- I9 a) V' I1 n* b+ L# k1 i // logs 1 // logs 2 // logs 34 o5 |/ f# D) r/ F // 直接输出了数组的元素+ f2 s1 @) [* r5 h 2 x/ L ]8 e) { //遍历对象 W$ Y- U; A; N+ t let profile = {name:"April",nickname:"二十七刻",country:"China"}; let keys = Object.keys(profile);4 G% W0 r, {7 _; h6 M$ Z6 u, w keys.forEach(i => { console.log(i) // 对象的键值; v6 q( G; T# T6 p6 m console.log(profile) // 对象的键对应的值 })# q0 t( U8 v( e; H& c& k" w8 } 6 q7 p0 n5 K/ |7 d 0 P2 @- ~$ V$ h# g" o8 V3 Y3 @ map 0 k9 P& C9 c9 m 我也是ES5版本发布的,我可以创建一个新数组,新数组的结果是原数组中的每个元素都调用一次提供的函数后的返回值。let arr = [1,2,3,4,5]; 3 ^/ F" }! U. S6 ` let res = arr.map(i => i * i);5 m6 W7 g/ E: E+ C' @; J console.log(res) // logs [1, 4, 9, 16, 25] $ {! m& B* f Y# U) L for...in枚举 4 F* m+ w, m; ?2 f 我是ES5版本发布的。以任意顺序遍历一个对象的除Symbol以外的可枚举属性。 7 f3 D! N& [0 w // 遍历对象* N5 q: @; Z' B let profile = {name:"April",nickname:"二十七刻",country:"China"}; for(let i in profile){: Q* o. o7 p [- g let item = profile;2 L, h6 |6 s9 z" D: a console.log(item) // 对象的键值8 T! p& X4 \% k% c7 Q: J console.log(i) // 对象的键对应的值7 S( M9 A7 F# j* } // 遍历数组 P/ g. T- O0 k0 d( \* j6 x let arr = ['a','b','c'];. Z" B+ X) ~* [! O- e# [5 [2 R' W for(let i in arr){ let item = arr; console.log(item) // 数组下标所对应的元素 console.log(i) // 索引,数组下标 7 P0 I. D1 v3 o // 遍历字符串/ f' A% `5 {. y+ b5 e9 x; s& R let str = "abcd" for(let i in str){6 E5 A3 L( s2 w7 l! Q let item = str; console.log(item) // 字符串下标所对应的元素3 d0 l& L# E4 g) }+ q console.log(i) // 索引 字符串的下标 } for...of迭代$ C% m/ S, e- h0 R. W) u 0 u/ r5 X K9 H' k5 k* C 我是ES6版本发布的。在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。4 k) f* h$ q+ J" p [7 e" g ! g5 _- a5 g s" z; P // 迭代数组数组 let arr = ['a','b','c'];8 u; ?* x5 y% z% q6 u for(let item of arr){5 M3 _/ x3 d8 w( \ console.log(item)' u: Z/ Y( c- M }' O. `4 S- j$ V- g // logs 'a'% M4 q9 s- S* B$ B* \7 N // logs 'b'$ h1 c: K$ w7 G/ \3 X7 A // logs 'c'; s4 r8 ~9 D" D* f$ d! K! i " i1 J8 y. ]5 m* o% B% d6 o // 迭代字符串% d- f) ~8 ~4 S& ?( I let str = "abc"; for (let value of str) { console.log(value);: p2 l; N; H' x; @0 d }5 ~6 x: K+ t- H$ X4 {2 w // logs 'a'( R; e3 ~& {: i: G2 k9 ?, B5 R) ~ // logs 'b' // logs 'c' ( g0 e# {& S5 i& ^, D // 迭代map+ ~) n: u4 @+ F" q* ~5 V let iterable = new Map([["a", 1], ["b", 2], ["c", 3]] for (let entry of iterable) { console.log(entry); }% G7 V" k4 s% G, ?4 g // logs ["a", 1] // logs ["b", 2]! ?; b! B( X( [! Z9 Y4 O' X5 c n // logs ["c", 3]' ?: ]" R/ \2 u) e& u * G0 W, L% ]2 S" H // 迭代map获取键值 for (let [key, value] of iterable) {) c% K8 X% n6 n9 j" W console.log(key), a- P& C7 U' ?# q- |* { console.log(value);/ ?8 e8 M" g8 z& n+ O) l/ d2 { }" T8 S9 q# d6 w( c. b% V5 S 8 R8 `5 Q1 `6 o- R // 迭代set, c6 H$ A8 m+ \/ E" P4 R) K8 J1 u5 w let iterable = new Set([1, 1, 2, 2, 3, 3,4]); for (let value of iterable) { console.log(value);8 c, y: b8 I" H6 B2 y5 i! ^; |2 p }) K% V5 ^; j" W: b K- u // logs 1 // logs 21 k; n2 G. d4 j" ^4 S* e1 j5 B // logs 3 // logs 4 * [3 X8 G6 F- A8 j8 ?" F( R // 迭代 DOM 节点% G' i0 |, L) c: e8 ?2 \ let articleParagraphs = document.querySelectorAll('.article > p');4 N9 y; `) h6 M for (let paragraph of articleParagraphs) { paragraph.classList.add("paragraph");2 I) R0 s p* Z; v8 B // 给class名为“article”节点下的 p 标签添加一个名为“paragraph” class属性。 }# b; N5 p1 T. O3 ?8 g // 迭代arguments类数组对象 (function() {& W7 N& B" x4 x8 \# K. Z( I; I0 z for (let argument of arguments) {& I3 B4 q0 m7 X: c console.log(argument); }+ _2 h% {9 Z% [ })(1, 2, 3);: B, j2 E) o8 S& |( o // logs:7 k* [+ a& T' l5 Y! e1 p // 18 j! }0 g" u h$ I' ~' K5 s# o // 2 // 36 l( l2 G& n; x% E( K# P 2 J2 J; Y' N9 |2 I. H // 迭代类型数组 let typeArr = new Uint8Array([0x00, 0xff]); for (let value of typeArr) { console.log(value);% V/ a% U& `* i# W4 c }2 ] e3 g% P/ P6 e2 O( f" S! G // logs:- U! r3 P. M% {; f- F$ m8 s // 0 // 2550 z+ R& H( I3 P 经过第一轮的自我介绍和技能展示后,我们了解到:
能力甄别作为一个程序员,仅仅认识他们是远远不够的,在实际开发中鉴别他们各自的优缺点。因地制宜的使用他们,扬长避短。从而提高程序的整体性能才是能力之所在。关于跳出循环体在循环中满足一定条件就跳出循环体,或者跳过不符合条件的数据继续循环其它数据。是经常会遇到的需求。常用的语句是break 与 continue。简单的说一下二者的区别,就当复习好了。
原理 :查看forEach实现原理,就会理解这个问题。7 m, R4 D" k9 ~) b 0 H: g- I! R0 J0 R0 [( y( O Array.prototype.forEach(callbackfn [,thisArg]{ } 传入的function是这里的回调函数。在回调函数里面使用break肯定是非法的,因为break只能用于跳出循环,回调函数不是循环体。 在回调函数中使用return,只是将结果返回到上级函数,也就是这个for循环中,并没有结束for循环,所以return也是无效的。$ R# y: l* H$ A map() 同理。 map()链式调用6 [- b, M% P4 m# x6 k& n map() 方法是可以链式调用的,这意味着它可以方便的结合其它方法一起使用。例如:reduce(), sort(), filter() 等。但是其它方法并不能做到这一点。forEach()的返回值是undefined,所以无法链式调用。 // 将元素乘以本身,再进行求和。+ k y7 L- @0 _* ^ let arr = [1, 2, 3, 4, 5];5 \- _( J, n) _4 }6 _1 P let res1 = arr.map(item => item * item).reduce((total, value) => total + value); ( T: Q5 N4 K" G7 C" g! U" o# _ console.log(res1) // logs 55 undefined" ) s: x' |& e' s/ c+ t! C- w for...in会遍历出原型对象上的属性0 s3 q( D/ U3 f- A6 f; k# j Object.prototype.objCustom = function() {}; Array.prototype.arrCustom = function() {}; var arr = ['a', 'b', 'c']; arr.foo = 'hello for (var i in arr) {% Y7 Q4 c) s- \: m; g7 y/ ]% Y5 _ console.log(i);4 S8 r* p" _, P6 y }8 W) ]7 N: L) R // logs // 0 // 1 // 21 ~ s( U4 _) u& r; C( R# t, L // foo0 l& N# p- s- K // arrCustom // objCustom+ v/ ?. j; i2 p) p L: d! p3 X6 h( Q, r0 B" l * Z& X9 Q Y# C3 K0 V% \0 P9 z 然而在实际的开发中,我们并不需要原型对象上的属性。这种情况下我们可以使用hasOwnProperty() 方法,它会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。如下:' u4 D3 k$ N2 Q# E Object.prototype.objCustom = function() {};+ X7 e# G& S7 P: H6 r4 r* u7 x Array.prototype.arrCustom = function() {}; var arr = ['a', 'b', 'c']; arr.foo = 'hello- S& y3 B8 Q+ e7 R for (var i in arr) {/ `& c& P8 o) U, \ g0 t# S if (arr.hasOwnProperty(i)) {; F( Q8 f T, y8 y" b console.log(i); } }4 X: C* w1 M J8 E // logs // 0 // 1 // 2! i3 Q D, R6 k" Z // foo+ q- `/ e( A- P- ? // 可见数组本身的属性还是无法摆脱。此时建议使用 forEach 对于纯对象的遍历,选择for..in枚举更方便;对于数组遍历,如果不需要知道索引for..of迭代更合适,因为还可以中断;如果需要知道索引,则forEach()更合适;对于其他字符串,类数组,类型数组的迭代,for..of更占上风更胜一筹。但是注意低版本浏览器的是配性。4 m: Y& L v: p& S A- B 0 l% e7 G3 b" L6 j. I * x @4 g/ m. u% x" f. u Q 性能 有兴趣的读者可以找一组数据自行测试,文章就直接给出结果了,并做相应的解释。5 u# n7 ?- a, [4 h9 n$ P1 z" f 2 \. k8 Z& j0 N \6 {7 L+ ` for > for-of > forEach > map > for-in
总结* j1 L& K1 g2 G 在实际开发中我们要结合语义话、可读性和程序性能,去选择究竟使用哪种方案。如果你需要将数组按照某种规则映射为另一个数组,就应该用 map。如果你需要进行简单的遍历,用 forEach 或者 for of。如果你需要对迭代器进行遍历,用 for of。如果你需要过滤出符合条件的项,用 filterr。如果你需要先按照规则映射为新数组,再根据条件过滤,那就用一个 map 加一个 filter。总之,因地制宜,因时而变。千万不要因为过分追求性能,而忽略了语义和可读性。在您的统治之下,他们5个只能是各自发挥长处,谁都别想称霸。% b0 C, a& {" Z |
微信公众号
手机版