请讲一讲JS中的 for...in 与 for...of (下)

发布于:2023-01-22 ⋅ 阅读:(306) ⋅ 点赞:(0)

start

  • 上一篇文章学习了一下 for…in;
  • 这篇文章就来学习一下 for…of ;
  • 以及总结一下两者的差异;

起因

正所谓日有所思夜有所梦,时常梦到for...of

起因是之前阅读过阮一峰老师的《ECMAScript 6 入门》,当时看到过这么一段内容如下图:ECMAScript 6 入门-18.Iterator 和 for…of 循环 原文链接

当初理解的不透彻,今天再来学习一下。

在这里插入图片描述

初步认识

先看看MDN的解释

MDN 的解释:

for…of 语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句

思考:

  1. 可迭代对象

    • 什么是可迭代对象? 它这里例举了一些可迭代对象(Array,Map,Set,String,TypedArray,arguments 对象等)

    • MDN 中是这样说:要成为可迭代对象, 一个对象必须实现 @@iterator 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性;

    • 《ECMAScript 6 入门》中是这样说:一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for…of 循环遍历它的成员。

打印一下 MDN 列举的那些可迭代对象,看它说的是否准确。(暂时就已数组为例)

在这里插入图片描述

验证

这里我偷个懒,直接打印 Symbol.iterator 属性。

// Array
var arr = [1, 2]
console.log(arr[Symbol.iterator])

// Map
var m = new Map()
console.log(m[Symbol.iterator])

// Set
var set = new Set()
console.log(set[Symbol.iterator])

// String
var str = '你好'
console.log(str[Symbol.iterator])

// TypedArray
var typeArr = new Uint8Array([0x00, 0xff])
console.log(typeArr[Symbol.iterator])

// arguments
;(function () {
  console.log(arguments[Symbol.iterator])
})(1, 2, 3)

/* 

[Function: values]
[Function: entries]
[Function: values]
[Function: [Symbol.iterator]]
[Function: values]
[Function: values]

*/

Symbol.iterator 属性

for…of 整个逻辑就是借助属性Symbol.iterator指向的函数。

这个函数执行后会返回一个对象;

返回的对象有 next 方法,每执行一次会返回一个对象;

例如{value: 10, done: false}

演示一下:

var arr = [1, 2, 3, 4]

let fn = arr[Symbol.iterator]()

console.log(fn.next())
console.log(fn.next())
console.log(fn.next())
console.log(fn.next())
console.log(fn.next())
console.log(fn.next())
/* 
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: undefined, done: true }
{ value: undefined, done: true }
*/

为了更好理解:我这里重写一下数组的 Symbol.iterator 属性

var arr = [1, 2, 3, 4, 5]

arr[Symbol.iterator] = function () {
  const self = this
  let index = 0
  return {
    next() {
      if (index < self.length) {
        return {
          value: self[index++] + '额外加工一下遍历的数据',
          done: false,
        }
      }
      return { value: undefined, done: true }
    },
  }
}

console.log(arr) // [ 1, 2, 3, 4, 5, [Symbol(Symbol.iterator)]: [Function] ]

for (const i of arr) {
  console.log(i)
}
/* 

1额外加工一下遍历的数据
2额外加工一下遍历的数据
3额外加工一下遍历的数据
4额外加工一下遍历的数据
5额外加工一下遍历的数据

*/

为什么对象没有属性 Symbol.iterator

for…of 遍历对象的时候会报错:Uncaught TypeError: obj is not iterable

原因:

摘抄自 《ECMAScript 6 入门》

对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。

调用 Iterator 接口的场合

  1. 数组和 Set 的解构赋值
  2. 扩展运算符
  3. for…of
  4. Array.from()
  5. Map(), Set(), WeakMap(), WeakSet()(比如 new Map([[‘a’,1],[‘b’,2]]))
  6. Promise.all()
  7. Promise.race()

for…of 与 for…in 的区别

无论是 for…in 还是 for…of 语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。

  • for…in 语句以任意顺序迭代对象的可枚举属性。

  • for…of 语句遍历可迭代对象定义要迭代的数据。

还有一个区别这里提一下,for…in 是 ES3 实现的,而 for…of 是 ES6 实现的,所以 for…of 在生产环境使用的时候大概率会被 babel 转换。

end

  • 写到这里,对这两个方法有一个初步的认知;
  • 其实也没必要说死记硬背:for…in 可以遍历什么,for…of 不能遍历什么。知道迭代的原理,比较好理解;
本文含有隐藏内容,请 开通VIP 后查看