for...in与for..of大比拼

236 阅读4分钟

前沿

我们在讲解for...in与for..of区别的前,我们先思考以下问题:

  • for...in可以遍历Map和Set对象吗?
  • for...of可以遍历对象吗?
  • for...in遍历的对象是有序的还是无序的?
  • for和forEach可以return和break吗?
  • 可以用 for of 遍历 Object 吗?

1.共性

for of 和 for in都是用来遍历的属性

2.区别

  • for...in可遍历对象和数组,不能遍历mapset。( 之所以不能 for-in 是因为 mapset 都没有index)
  • for...in得到的是 对象的key,数组和字符串的下标
  • for...in不仅遍历数组中的元素,还会遍历自定义的属性,甚至原型链上的属性都被访问到.
  • for...of和forEach一样,是直接得到值
  • for...of可遍历数组和其他可迭代对象(eg: Map、Set),不能用于对象(Why? 见4详解)
  • for...of不会遍历到自定义属性和原型链上的属性

1.两者对比例子(遍历对象)

const obj = { a: 1, b: 2, c: 3 }
for (let i in obj) {
    console.log(i)    //输出 : a   b  c
}
for (let i of obj) {
    console.log(i)    //输出: Uncaught TypeError: obj is not iterable 报错了
}

2.两者对比例子(遍历数组)

const arr = ['a', 'b', 'c']
for (let i in arr) {
   console.log(i)         //输出  0  1  2
}
for (let i of arr) {
   console.log(i)         //输出  a   b   c
}

3.两者对比例子(遍历Map对象和Set对象)

// set
const set = new Set(['1', '2', '1', '3']) // 有去重效果哦
for (const val of set) {
    console.log(val); // 1 2 3
}
// map
const map = new Map([['1', 'one'], ['2', 'two']]);
map.set('3','three')
map.get('3') // three
map.delete('3')

console.log(map); // Map(2) { '1' => 'one', '2' => 'two' }
for (const i of map) {
    console.log(i, ' --- ', i[0], i[1]); // [ '1', 'one' ]--- 1 one  // [ '2', 'two' ]  ---  2 two
}
for (const [k, v] of map) {
    console.log('k-', k, 'v-', v); // k- 1 v- one // k- 2 v- two
}

3.特点

for in 特点

for … in 循环返回的值都是数据结构的 键值名(即下标)。 遍历对象返回的对象的key值,遍历数组返回的数组的下标(key)。 for … in 循环不仅可以遍历数字键名,还会遍历原型上的值和手动添加的其他键。 特别情况下, for … in 循环会以看起来任意的顺序遍历键名 for in 的 常规属性和 排序属性

在ECMAScript规范中定义了 「数字属性应该按照索引值⼤⼩升序排列,字符串属性根据创建时的顺序升序排列。」在这⾥我们把对象中的数字属性称为 「排序属性」,在V8中被称为 elements,字符串属性就被称为 「常规属性」, 在V8中被称为 properties。

const obj = {
 '9': '9',
 'B': 'B',
 '1': '1',
 'A': 'A'
}
for(key in obj){
 console.log(`index:${key} value:${obj[key]}`)
}
// index:1 value:1
// index:9 value:9
// index:B value:B
// index:A value:A
总结一句: for in 循环特别适合遍历对象。

for of 特点

for of 循环用来获取一对键值对中的值,而 for in 获取的是 键名 一个数据结构只要部署了 Symbol.iterator 属性, 就被视为具有 iterator接口, 就可以使用 for of循环。 for of 不同与 forEach, 它可以与 break、continue和return 配合使用,也就是说 for of 循环可以随时退出循环。

4. 为什么for...of不能遍历对象

因为能够被for...of正常遍历的,都需要实现一个遍历器Iterator。而数组、字符串、Set、Map结构,早就内置好了Iterator(迭代器),它们的原型中都有一个Symbol.iterator方法,而Object对象并没有实现这个接口,使得它无法被for...of遍历。

Array.prototype[Symbol.iterator];// ƒ values() { [native code] }
String.prototype[Symbol.iterator];// ƒ [Symbol.iterator]() { [native code] }
Set.prototype[Symbol.iterator];// ƒ values() { [native code] }
Map.prototype[Symbol.iterator];// ƒ entries() { [native code] }
Object.prototype[Symbol.iterator];// undefined

拓展- Iterator 的遍历过程

我们知道数组是支持for...of循环的,那数组肯定部署了 Iterator 接口,我们通过数组来看看Iterator 的遍历过程。 image.png

从图中我们能看出:

  1. Iterator 接口返回了一个有next方法的对象。
  2. 每调用一次 next,依次返回了数组中的项,直到它指向数据结构的结束位置。
  3. 返回的结果是一个对象,对象中包含了当前值value 和 当前是否结束done

5.那么我们如何用for of遍历对象?

我们先看下使用for of遍历对象,如下: image.png

提示报错内容是:obj is not iterable.如若我们给对象也部署 Iterator 接口(其实就是在Object.prototype上实现一个以Symbol.iterator为名的function,这个function返回一个有next方法的对象,每调用一次 next, 能够依次返回数组中的项,直到它指向数据结构的结束位置 ) 实践来喽 image.png image.png

6.总结for,for..in, foreach, for..of缺点

for循环:如for(int i=0;i<5;i++){}。缺点为书写比较麻烦。注意:for循环中可以return;

for in :缺点比较明显,它不仅遍历数组中的元素,还会遍历自定义的属性,甚至原型链上的属性都被访问到。此外,它遍历效率比较低;

forEach:不能 break 和 return;

for of:与 forEach 不同的是,它可以正确响应 break、continue 和 return 语句。它不仅可以遍历数组,还可以遍历类数组对象和其他可迭代对象(如map对象)。并且它不会遍历自定义属性,这点可以区别于for in; 注意:for of无法遍历对象;