for
,for in
, for of
和forEach
四者都是用于循环的,但是每个使用场景都是有所不同,接下来进行一个对比
1. 可枚举对象
const person = {
name: 'henry',
age: 26,
};
for (const key in person) {
console.log(key);
}
//输出结果:name age
for (const value of person) {
console.log(value);
}
// TypeError: person is not iterable
person.forEach((value, index, array) => {
console.log(value)
})
// TypeError: person.forEach is not a function
这些结果可以看出,只有for…in可以对对象进行遍历(遍历的是对象的key),而for…of和forEach都不能对对象进行遍历,for的编写方式更是没办法遍历
2.可枚举数组
Array.prototype.sayHello = function() {
console.log("Hello")
}
Array.prototype.str = 'world';
var arr = ['a', 'b', 'c'];
arr.name = '数组';
for (const key in arr) {
console.log(key);
console.log(typeof key);
console.log(arr[key]);
}
// 输出的是:
// 0 string a
// 1 string b
// 2 string c
// name string 数组
// sayHello string [Function]
// str string world
for (const value of arr) {
console.log(value);
}
// 输出:a,b,c
arr.forEach((value, index) => {
console.log(index)
console.log(typeof index);
console.log(value)
})
// 输出的是:
// 0 number a
// 1 number b
// 2 number c
这些结果看出以下结论:
- 使用for…in是输出索引值,且索引值是string类型,不仅返回的是数组的下标,而且将数组的原型对象以及数组对象本身属性值都会返回;通过索引值能拿到数组数据;
- 使用forEach可以输出索引值和数组值,而且不会输出数组的原型对象;
- 使用for of无法输出索引值,但也不会输出数组的原型对象。
- 使用for可以输出索引值和数组值,而且不会输出数组的原型对象;
在实际工作开发中,数组的原型对象很可能是不需要的,全部列举出来可能会产生新的问题。
为了解决原型对象这个问题,可以使用hasOwnProperty
for(let key in arr){
if(arr.hasOwnProperty(key)) {
console.log(key);
}
}
//输出的结果是 0 1 2 name
这个结果能看出来,虽然使用hasOwnProperty,但是数组本身的属性还是会输出
3.迭代字符串
let str = 'hello';
for (let i = 0; i < str.length; i++) {
console.log(str[i]);
}
//输出结果是 h e l l o
for (let value of str) {
console.log(value);
}
// //输出结果是 h e l l o
for (let key in str) {
console.log(str[key]);
}
// 输出的结果是 h e l l o
str.forEach(function(value, index) {
console.log(value);
});
//输出: TypeError: str.forEach is not a function
结论:for,for…in,for…of可以循环字符串,而forEach不可以
4.迭代arguments类数组对象
(function() {
for (let argument of arguments) {
console.log(argument);
}
//输出结果是 11 22 33
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
// //输出结果是 11 22 33
for (let key in arguments) {
console.log(arguments[key]);
}
//输出结果是 11 22 33
arguments.forEach(function(value, index) {
console.log(value);
});
//输出:TypeError: arguments.forEach is not a function
})(11, 22, 33);
结果可以得出结论:forEach无法迭代arguments,而for,for…in,for…of都可以迭代arguments
5.迭代map和set
let mapData = new Map([
['a', 1],
['b', 2],
['c', 3]
]);
for (let [key, value] of mapData) {
console.log(key);
console.log(value);
}
//输出结果是 a 1 b 2 c 3
for (let item in mapData) {
console.log(item);
}
//没有输出结果
for (let i = 0; i < mapData.length; i++) {
console.log(mapData[i]);
}
//没有输出结果
mapData.forEach(function(value, index) {
console.log(value);
console.log(index);
});
//输出结果是 a 1 b 2 c 3
let setData = new Set([11, 22, 33])
for (let value of setData) {
console.log(value);
}
//输出结果是 11 22 33
for (let item in setData) {
console.log(item);
}
//没有输出结果
for (let i = 0; i < setData.length; i++) {
console.log(setData[i]);
}
//没有输出结果
setData.forEach(function(value,index) {
console.log(index);
console.log(value);
});
//输出结果是 11 11 22 22 33 33
结果可以得出结论:forEach和for…of都可以迭代map/set,而for,for…in无法迭代map/set;
这里有个点要注意,set的forEach中第一个参数是value值,第二个值依然是value值;map的forEach中第一个参数是value值,第二个值是对应的key值
6.是否可中断
var arr = [11, 22, 33];
arr.forEach(function(value, index) {
console.log(index)
if (value === 22) {
return false;
}
console.log(value);
});
//输出的结果是 0 11 1 2 33
//使用continue输出: SyntaxError: Illegal continue statement: no surrounding iteration statement
//使用break输出:SyntaxError: Illegal break statement
for (let key in arr) {
console.log(arr[key]);
if (arr[key] == 22) {
continue;
}
console.log(key);
}
// 输出的结果是 11 0 22 33 2
for (let value of arr) {
console.log(value);
if (value == 22) {
break;
}
}
//输出结果是 11 22
这些结果看出以下结论:
- forEach不能退出循环本身,return只能控制跳出单次循环,不支持break和continue,不可中断循环
- for…in,for…or以及for都可以中断循环
7.性能效率对比(仅对比连续且数据量大的无漏数组)
var arr = []
console.time('write array')
for (var i = 0; i < 1000 * 1000; i++) {
arr.push(i)
}
console.timeEnd('write array')
console.time('for loop read')
var sum0 = 0
for (var j = 0; j < 1000 * 1000; j++) {
sum0 += arr[j]
}
console.timeEnd('for loop read')
console.time('while read')
var sum1 = 0, index = 0
while (index < 1000 * 1000) {
sum1 += arr[index]
index++
}
console.timeEnd('while read')
console.time('for in read')
var sum2 = 0
for (let key in arr) {
sum2 += arr[key]
}
console.timeEnd('for in read')
console.time('for of read')
var sum3 = 0
for (let value in arr) {
sum3 += value
}
console.timeEnd('for of read')
console.time('for each read')
var sum4 = 0
arr.forEach(function (item) {
sum4 += item
})
console.timeEnd('for each read')
打印的结果是:
write array: 18.042ms
for loop read: 3.838ms
while read: 3.686ms
for in read: 183.033ms
for of read: 204.108ms
for each read: 16.922ms
多次执行后出入不大,由此可得出结论:
性能效率上:for > for each > for in > for of
部分介绍性能时,考虑的数组数据量和连续性和本次实验的不一致,所有结果也有所差别,过分纠结并没太多意义,只需记得数据量大且无漏时,用for循环遍历数组效率快,且最好把判断用的长度先计算算并缓存起来
总结
- for…in 适用于纯对象的遍历,能输出可枚举属性,不太适用数组,不能用于map/set对象
- for…of 适用于无需知道索引值的数组遍历,另外对于字符串,arguments类数组,map/set对象的迭代也更适用
- forEach适用于需要知道索引值的数组遍历,可遍历map/set对象,但是最大的特点是不能中断
- for循环适用于数据量大,或者兼容性要求高的情况
for…of其实也可以遍历对象的,但是需要通过Symbol.iterator给要遍历的对象添加这个塑性,这涉及到了另一些东西,有兴趣的可以点击 用symbol.iterator实现用for…of遍历对象,这篇文章
使用 for…in 循环遍历对象的属性时,原型链上的所有属性都将被访问,所以推荐总是使用 hasOwnProperty 方法, 这将会避免原型对象扩展带来的干扰
PS:另有一些不太影响使用场景的特性
async function tmd(value) {
await new Promise((resolve) => {
setTimeout(() => {
console.log(value)
resolve()
}, 1000)
})
}
let arr = [1, 2, 3]
async function folog() {
for (let value of arr) {
await tmd(value)
}
}
async function filog() {
for (let i in arr) {
await tmd(arr[i])
}
}
async function forlog() {
for (let i = 0; i < arr.length; i++) {
await tmd(arr[i])
}
}
async function felog() {
arr.forEach(async function(value, index, array) {
await tmd(value)
})
}
// folog(); //每隔1000毫秒打印一个数字
// filog(); //每隔1000毫秒打印一个数字
// forlog(); //每隔1000毫秒打印一个数字
// felog(); //1000毫秒后全部打印出来
可以得出结论:for,for…in,for…of是继发性的,而forEach是并发性的