常用的遍历方法有哪些?每种适用于什么场景,各有什么优缺点?
常用遍历方法
for循环
语法:
for (语句 1; 语句 2; 语句 3) { 被执行的代码块 }
语句 1 在循环(代码块)开始前执行
语句 2 定义运行循环(代码块)的条件
语句 3 在循环(代码块)已被执行之后执行
for…in
for...in
语句以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。
语法:
for (variable in object) {…}
variable:在每次迭代时,将不同的属性名分配给变量。
object:被迭代枚举其属性的对象。
注意:for...in
不应该用于迭代一个 Array
for...in
不仅可以遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。(即:for...in
循环语句将返回所有可枚举属性,包括非整数类型的名称和继承的那些。)- 不能保证
for...in
将以任何特定的顺序返回索引。
应用:1
2
3
4
5var obj = {a:1, b:2, c:3};
for (var prop in obj) {
console.log("obj." + prop + " = " + obj[prop]);
}
for…of
for...of
语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。
语法:
for (variable of iterable) {
//statements
}
variable:在每次迭代中,将不同属性的值分配给变量。
iterable:可枚举其枚举属性的对象。
注意:可以使用break
跳出循环
应用:1
2
3
4
5
6
7
8
9
10let iterable = [10, 20, 30];
for(let a of iterable){
if(a > 20) {
break
}
console.log(a)
}
// 10
// 20
forEach()
forEach
方法对数组的每个元素执行一次提供的函数。
语法:
array.forEach(callback(currentValue, index, array){
//do something
}, this)
array.forEach(callback[, thisArg])
参数:
currentValue(当前值):数组中正在处理的当前元素。
index(索引):数组中正在处理的当前元素的索引。
array:forEach()方法正在操作的数组。
thisArg:可选参数。当执行回调 函数时用作this的值(参考对象)。
应用:1
2
3
4//打印数组
const arr = ['a', 'b', 'c'];
arr.forEach( element => console.log(element));
1 | //复制对象 |
缺点:
不能中途跳出循环,如果数组在迭代时被修改了,则其他元素会被跳过。1
2
3
4
5
6
7
8
9
10var words = ["one", "two", "three", "four"];
words.forEach(function(word) {
console.log(word);
if (word === "two") {
words.shift();
}
});
// one
// two
// four
map()
map
方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
语法:
arr.map(function callback(currentValue, index, array) {
// Return element for new_array
}[, thisArg])
参数:
callback: 生成新数组元素的函数,使用三个参数:
currentValue callback: 的第一个参数,数组中正在处理的当前元素。
index callback: 的第二个参数,数组中正在处理的当前元素的索引。
array callback: 的第三个参数,map方法被调用的数组。
thisArg: 可选的。执行 callback 函数时 使用的this 值。
返回值:
一个新数组,每个元素都是回调函数的结果。
应用:1
2
3
4//重新格式化数组
var numbers = [1, 4, 9];
var roots = numbers.map(Math.sqrt);
// roots的值为[1, 2, 3], numbers的值仍为[1, 4, 9]
缺点: 不能中途跳出循环。
性能测试
测试环境:node v9.8.0
测试代码:
1 | 测试环境:node v9.8.0 |
测试结果(ms为单位):
数组长度 | 100 | 1000 | 10000 | 100000 | 1000000 |
---|---|---|---|---|---|
普通for循环 | 0.101 | 0.125 | 0.343 | 0.224 | 4.778 |
倒序for循环 | 0.009 | 0.030 | 0.255 | 8.606 | 4.391 |
while循环 | 0.007 | 0.028 | 0.24 | 23.957 | 4.445 |
for-in | 0.032 | 0.351 | 2.466 | 22.203 | 163.261 |
for-each | 0.030 | 0.054 | 0.296 | 1.986 | 14.921 |
map | 0.029 | 0.055 | 0.323 | 19.905 | 189.387 |
思考:我们选择循环的多个角度——业务需要、代码可读性、性能。
小结: 多数情况下前端不会涉及到大数量级的运算,如果有,那么应该考虑业务代码的正确性或者是否该将运算交到后端处理,在小数据量的运算中优先考虑代码的可读性、可维护性:采用for-each
和map
方法。
延伸阅读: 为什么for-in效率相比而言这么低?for...in
一般是用在对象属性名的遍历上的,由于每次迭代操作会同时搜索实例本身的属性以及原型链上的属性,所以效率肯定低下。for-in实际上效率是最低的。这是因为 for...in
有一些特殊的要求,具体包括:
- 遍历所有属性,不仅是
ownproperties
也包括原型链上的所有属性。 - 忽略
enumerable
(可枚举) 为false
的属性。 - 必须按特定顺序遍历,先遍历所有数字键,然后按照创建属性的顺序遍历剩下的。
相关链接:
吹毛求疵的追求优雅高性能JavaScript
Array-MDN
Vue是如何实现响应式的?监测变化时应注意什么?
如何追踪变化
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data
选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty
把这些属性全部转为 getter/setter。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器。
每个组件实例都有相应的 watcher
实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter
被调用时,会通知 watcher
重新计算,从而致使它关联的组件得以更新。
监测变化时应注意:
Vue 不能检测到对象属性的添加或删除。
1
2
3
4
5
6
7
8
9var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 现在是响应式的
vm.b = 2
// `vm.b` 不是响应式的不能检测以下变动的数组:
- 当你利用索引直接设置一个项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength