for in 和 for of的区别

371 阅读4分钟

两者的区别

在前端中,for infor 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