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


    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]
    
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    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 }
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    为了更好理解:我这里重写一下数组的 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额外加工一下遍历的数据
    
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    为什么对象没有属性 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 不能遍历什么。知道迭代的原理,比较好理解;
  • 相关阅读:
    【nodejs】express-generator项目--创建接口及数据库连接
    从源码看vue(v2.7.10)中的v-bind的原理
    适配移动端,菜单采用底部固定形式(1+X Web前端开发初级 例题)
    python基础
    ArcGIS Pro SDK 002 对数据文件的读取和操作
    运行软件报错mfc140.dll丢失?分享mfc140.dll丢失的解决方法
    说一说MVCC多版本并发控制器?
    【Spring】MyBatis(缓存机制、二级缓存、插件)面试题
    LeetCode刷题之分隔链表(图解➕代码)
    深入理解MySQL——配置半同步复制
  • 原文地址:https://blog.csdn.net/wswq2505655377/article/details/126273378