考察点分析

本题主要考察以下三个核心维度:

  1. 迭代协议理解:区分可迭代协议(Iterable Protocol)与迭代器协议(Iterator Protocol)的差异
  2. 循环机制原理:掌握for…in(枚举属性)与for…of(迭代值)的底层实现差异
  3. 原型扩展能力:通过实现Symbol.iterator使普通对象支持迭代协议

具体技术评估点:

  • 属性枚举与值迭代的底层机制差异
  • Symbol.iterator接口的实现规范
  • 迭代器对象的next()方法控制逻辑
  • 原型链对属性遍历的影响
  • 生成器函数在迭代协议中的应用

技术解析

关键知识点

Symbol.iterator > 迭代器协议 > 属性枚举顺序 > 生成器函数

原理剖析

for…in

  • 基于对象属性描述符的[[Enumerate]]机制
  • 遍历对象自身及原型链上的可枚举属性(enumerable:true)
  • 输出顺序:当前对象属性按数字升序 → 字符串插入序 → Symbol插入序

for…of

  • 调用对象的Symbol.iterator方法获取迭代器
  • 通过迭代器对象的next()方法按需获取值
  • 遵循可迭代协议(返回包含done/value的对象)

常见误区

  1. 混淆属性遍历与值迭代的顺序(如数组索引的特殊处理)
  2. 忽视通过Object.defineProperty设置的enumerable属性对for…in的影响
  3. 未正确处理迭代器终止状态(无限循环)

问题解答

实现差异

  • for…in遍历对象的可枚举字符串键,包含原型链,输出顺序由ECMA规范定义
  • for…of调用@@iterator方法,按迭代器协议逐个取值,不遍历原型链

实现方案

  const plainObject = {
  a: 1,
  b: 2,
  // 实现迭代协议
  [Symbol.iterator]: function() {
    const keys = Object.keys(this) // 仅获取自身可枚举属性
    let index = 0
    return {
      next: () => ({
        value: this[keys[index++]], // 返回属性值
        done: index > keys.length
      })
    }
  }
}

// 使用示例
for(const val of plainObject) {
  console.log(val) // 输出1, 2
}
  

解决方案

编码示例优化

  function makeIterable(obj) {
  obj[Symbol.iterator] = function* () {
    for(const key of Object.keys(this)) { 
      yield this[key] // 使用生成器简化迭代逻辑
    }
  }
  return obj
}

const optimizedObj = makeIterable({x: 10, y: 20})
console.log([...optimizedObj]) // [10, 20]
  

优化说明

  • 时间复杂度:O(n)(线性遍历)
  • 空间复杂度:O(n)(Object.keys临时存储)
  • 生成器自动维护迭代状态,避免手动管理索引

扩展性建议

  • 大数据量:改用属性遍历+迭代器模式(按需生成值)
  • 低端设备:避免在迭代中创建大型临时数组(如用while替代Object.keys)
  • 类型扩展:支持Map式[key, value]迭代(返回entries而非values)

深度追问

  1. 如何实现对象的逆序遍历? 修改Symbol.iterator实现,反转keys数组后遍历

  2. for…of循环中break语句的底层处理? 迭代器需实现return()方法进行资源清理

  3. 如何让类数组对象支持Array.from()? 同时实现length属性和索引访问(或直接添加Symbol.iterator)

Last updated 06 Mar 2025, 13:07 +0800 . history