两者的区别
在前端中,for in
和 for of
都是用来遍历数组或则对象的,两者之间的差别,看下面的这个例子
const obj = {
a: 'test-a',
c: 'test-c',
b: 'test-b',
};
for (let key in obj) {
console.log(key)
} // a,b,c
for (let key of obj) {
console.log(key)
} // Uncaught TypeError: obj is not iterable
可以看到使用 for in
遍历对象的时候,输出的是对象的键,使用for of
遍历对象的时候,直接报错了,所以,for of
不能用来遍历对象(这里暂时这样说,其实质并不是不可以遍历对象,看报错信息可以看到,是obj is not iterable
)
上面是遍历对象,接下来看遍历数组
const arr = [1, 2, 3, 4, 5];
for (const key in arr) {
console.log(key);
} //0,1,2,3,4
for (const key of arr) {
console.log(key);
} //1,2,3,4,5
可以看到for in
遍历数组输出的是数组的下标,而for of
输出的是数组的value
值
再看一个例子:
const arr = ['name', 'age'];
arr.type = 'test';
for (const key in arr) {
console.log(key);
} // 0,1,type
for (const key of arr) {
console.log(key);
} // name age
function Bar(name) {
this.name = name;
}
const bar = new Bar('xiaoming');
Bar.prototype.type = '人';
for (const key in bar) {
console.log(key);
} // name、type
console.log(bar) //{name: xiaoming}
区别总结
可以看到,后面给arr
自定义添加的属性,并没有通过for of
遍历得到,而for in
却能取到其键,而且在后面的Bar
的原型上添加的属性,仍然是遍历出来了
综合上面的例子,得出:
for in
的特点
for in
遍历返回的是数据解构的键名,遍历数组返回的是数组的下标for in
不仅可以遍历对象的属性,还会遍历对象原型上添加的属性和自定义添加的属性for of
不能遍历对象for in
在某些情况下,会出现顺序错乱的问题 这里说一下for in顺序错乱的问题,其实也不是遍历无顺序,而是有规律的,这就涉及到常规属性和排序属性了,下面简单的数一下什么是常规属性和排序属性,看个例子
function Foo() {
this[100] = 'test-100';
this[10] = 'test-10';
this[1] = 'test-1';
this.A = 'test-A';
this.B = 'test-B';
this.C = 'test-C';
}
const foo = new Foo();
for (const key in foo) {
console.log(foo[key]);
}
// 输出
test-1
test-10
test-100
test-A
test-B
test-C
可以看到,输出的结果并不是我们之前添加属性的时候的顺序,键为数字的,按照数字的大小升序输出,字符串则是按照我们设置的顺序输出
之所以出现这个结果,是因为ECMAscript规范中定义了数字属性应该按照索引值⼤⼩升序排列,字符 串属性根据创建时的顺序升序排列。在这里,我们把对象中的数字属性成为排序属性,字符串属性就是常规属性,所以上面遍历对象时,输出结果会跟设置属性时的顺序不一致
另外值得一提的是,排序属性在V8中被成为elements
,常规属性被成为properties
,在V8内部,为了有效的提升这两种属性的存储和访问的性能,使用了两种线性数据结构来分别保存这两种属性,在elements
属性中,会按照顺序存放排序属性,在properties
中会按照创建时的顺序存储
总结来说就是:for in
适合遍历对象,并且会遍历其原型上的属性
for of的特点
for of
遍历用来获取一对键值对中的值- 一个数据只要有
Symbol.iterator
属性,就被认为具有iterator接口,就可以使用for of
遍历 for of
不同于forEach
,它可以和 break、continue和return 配合使用,也就是for of
可以被打断,以及跳出当次循环- 提供了遍历所有数据结构的统一接口
那到底哪些数据拥有
Symbol.iterator
属性呢? - 数组Array
- Map
- Set
- String
- arguments
- NodeList(就是获取的dom集合)
上面这些数据结构都可以使用
for of
遍历,具有iterator接口的数据结构,也可以使用数组的扩展运算符和进行解构赋值操作
如果也想让对象使用for of
遍历呢?
- 可以使用
Object.keys
取到对象的keys集合,或则使用Object.values
取到对象的value集合,再使用for of
遍历 - 也可以给一个对象部署Symbol.iterator属性
const obj = {
100: 'test-100',
10: 'test-10',
1: 'test-1',
a: 'test-a',
c: 'test-c',
b: 'test-b',
};
obj[Symbol.iterator] = function () {
const iterator = { next };
const current = this;
const keys = Object.keys(current);
let index = 0;
function next() {
if (index < keys.length) {
return {
value: keys[index++], // 注意,这里返回的是key,所以下面打印出来的也就是key
done: false,
};
}
return {
done: true,
};
}
return iterator;
};
for (const key of obj) {
console.log(key);
}
输出
1
10
100
a
c
b
因为上面在next方法中,value值是取的key,所以使用for of
遍历之后打印出来的也是key