迭代器、生成器、元编程✅
iterator 是什么?
答案
Iterator(迭代器)是 JavaScript 中用于遍历数据结构的一种协议。它提供了一种统一的方式来访问集合中的元素,而不需要关心集合的具体实现细节。Iterator 协议定义了一个对象,该对象包含一个 next()
方法,每次调用该方法都会返回集合中的下一个元素。
Iterator 协议的核心是实现了 next()
方法的对象,这个方法返回一个包含两个属性的对象:
value
:当前元素的值。done
:一个布尔值,表示是否已经遍历完所有元素。如果done
为true
,则表示没有更多元素可供迭代。Symbol.iterator
:一个方法,返回一个新的迭代器对象。for...of
循环可以直接使用 Iterator 协议来遍历集合。- 示例:
const arr = [1, 2, 3]
const iterator = arr[Symbol.iterator]()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 3, done: false }
console.log(iterator.next()) // { value: undefined, done: true }
Iterator 协议的主要作用是提供一种统一的方式来遍历各种数据结构,如数组、字符串、Set、Map 等。通过实现 Iterator 协议,开发者可以自定义自己的数据结构,使其能够被 for...of
循环等语法直接遍历。Iterator 还可以与其他 ES6 特性(如生成器、解构赋值等)结合使用,增强代码的可读性和可维护性。
用过 generator ,讲一下?
答案
Generator(生成器)是 ES6 引入的一种特殊函数,具备“可暂停、可恢复”的执行特性。调用 Generator 函数不会立即执行,而是返回一个迭代器对象(Generator 对象),通过调用其 .next()
方法,函数体才会逐步执行,每次遇到 yield
语句暂停,并返回对应的值。
Generator 函数的声明方式为 function*
,内部通过 yield
关键字实现“分段执行”。
示例:
function * gen () {
yield 1
yield 2
yield 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }
-
实现自定义迭代器
Generator 天然实现了迭代器协议,可以用来生成序列、遍历数据结构等。 -
异步流程控制
通过yield
暂停异步操作,配合自动执行器(如 co 库)或手动调用.next()
,实现“同步写法的异步流程”,提升代码可读性。function * asyncGen () {
const data1 = yield fetchData1()
const data2 = yield fetchData2(data1)
return data2
} -
协程/任务分片
可将复杂任务拆分为多个步骤,逐步执行,避免阻塞主线程。 -
与 for...of、解构等配合
Generator 返回的对象可直接用于 for...of、解构赋值等场景。for (const x of gen()) {
console.log(x)
}
注意事项
- 只能由
function*
声明,不能用箭头函数。 - 每次调用
.next()
,从上次暂停处继续执行,直到下一个yield
或函数结束。 yield
只能在 Generator 函数内部使用。- 可以通过
.next(value)
向上一个yield
表达式传值,实现双向通信。 - 异常处理:可用
try/catch
捕获 Generator 内部异常,也可用.throw()
向生成器抛出异常。 - 与 async/await 区别:Generator 更灵活,适合复杂控制流;async/await 更适合简单 Promise 链式异步。
- Generator 返回的对象本身就是可迭代的(实现了
[Symbol.iterator]
),可与解构、扩展运算符等配合。
Generator 提供了“可暂停、可恢复”的函数执行机制,适合实现自定义迭代器、异步流程控制、协程等高级场景。使用时需注意语法规范、异常处理和与异步工具的配合,能极大提升代码的灵活性和可读性。
用过 Proxy 么,简单讲解下?
答案
Proxy
是 ES6 引入的用于拦截和自定义对象基本操作(如属性读取、赋值、函数调用等)的机制。通过 Proxy
,可以在对象操作前后插入自定义逻辑,实现如数据校验、虚拟属性、私有属性保护、日志记录等功能。
定义与用法
const proxy = new Proxy(target, handler)
-
target
:被代理的对象。 -
handler
:包含拦截方法(trap)的对象,如get
、set
、has
、deleteProperty
、apply
、construct
等。
常用拦截方法
方法 | 说明 |
---|---|
get | 读取属性时触发 |
set | 设置属性时触发 |
has | in 操作符时触发 |
deleteProperty | delete 操作符时触发 |
ownKeys | 枚举属性时触发 |
apply | 代理函数调用时触发 |
construct | 代理构造函数调用时触发 |
基础示例
const obj = { name: 'Alice' }
const proxy = new Proxy(obj, {
get (target, key) {
console.log(`读取属性 ${key}`)
return target[key]
},
set (target, key, value) {
console.log(`设置属性 ${key} 为 ${value}`)
target[key] = value
return true
}
})
proxy.name // 控制台输出:读取属性 name
proxy.name = 1 // 控制台输出:设置属性 name 为 1
典型应用场景
- 数据校验:拦截属性赋值,校验数据合法性。
- 虚拟/计算属性:动态生成属性值。
- 私有属性保护:限制对特定属性的访问。
- 日志/调试:记录对象操作。
- 深度监听:递归代理嵌套对象,实现深层变更监听。
注意事项
Proxy
只能代理直接对象,嵌套对象需递归代理才能深度监听。- 性能相较于
Object.defineProperty
略低,频繁操作大对象需谨慎。 - 兼容性有限,IE 不支持。
- 代理对象与原对象引用不同,注意引用比较和序列化等场景。
与 Object.defineProperty
区别
方面 | Proxy | Object.defineProperty |
---|---|---|
作用范围 | 整个对象及其所有操作 | 单个属性 |
能力 | 拦截多种操作 | 仅能拦截属性的读写 |
深度监听 | 支持(需递归实现) | 不支持 |
兼容性 | 新浏览器,IE 不支持 | 兼容性好 |
性能 | 相对较低 | 相对较高 |
示例:深度代理嵌套对象
function deepProxy (obj) {
return new Proxy(obj, {
get (target, key, receiver) {
const value = Reflect.get(target, key, receiver)
if (typeof value === 'object' && value !== null) {
return deepProxy(value)
}
return value
},
set (target, key, value, receiver) {
console.log(`设置属性 ${key} 为 ${value}`)
return Reflect.set(target, key, value, receiver)
}
})
}
const data = { info: { age: 18 } }
const p = deepProxy(data)
p.info.age = 20 // 控制台输出:设置属性 age 为 20
总结
Proxy 提供了强大的对象操作拦截能力,适合需要灵活定制对象行为的场景,但需注意性能和兼容性。
用过 Reflect 么,简单讲解下?
答案
Reflect
是 ES6 引入的内置对象,提供了一组与对象操作相关的方法,目的是让对象的底层操作变得更规范和函数化。它的方法与 Object
上的部分方法类似,但行为更一致,返回值更明确,且与 Proxy
的拦截器一一对应。
方法 | 作用 |
---|---|
Reflect.get(target, property, receiver) | 获取对象属性值,支持指定 this 上下文 |
Reflect.set(target, property, value, receiver) | 设置对象属性值,支持指定 this 上下文 |
Reflect.has(target, property) | 判断属性是否存在,等价于 property in target |
Reflect.deleteProperty(target, property) | 删除对象属性,等价于 delete target[property] |
Reflect.ownKeys(target) | 返回对象自身所有属性(包括 Symbol 和不可枚举属性) |
Reflect.getPrototypeOf(target) / Reflect.setPrototypeOf(target, proto) | 获取/设置对象原型 |
Reflect.isExtensible(target) / Reflect.preventExtensions(target) | 判断/设置对象是否可扩展 |
Reflect.defineProperty(target, property, descriptor) / Reflect.getOwnPropertyDescriptor(target, property) | 定义/获取属性描述符 |
Reflect.apply(target, thisArg, args) | 调用函数,等价于 Function.prototype.apply |
Reflect.construct(target, args, newTarget) | 构造实例,类似于 new target(...args) |
- 与
Proxy
结合:在Proxy
的 handler 中调用对应的Reflect
方法,可以保证默认行为和一致性。 - 更安全的对象操作:
Reflect
方法返回布尔值或明确的结果,不会抛出异常,便于错误处理。 - 统一的 API 风格:所有操作都是函数调用,便于元编程和自动化处理。
注意事项
Reflect
只提供静态方法,不能作为构造函数使用。- 与
Object
方法相比,Reflect
方法的返回值更规范(如Reflect.set
返回布尔值)。 - 频繁操作原型或扩展性可能影响性能,建议按需使用。
- 主要用于底层对象操作、元编程、代理拦截等高级场景,普通属性访问仍推荐直接点操作。
const obj = { a: 1 }
const proxy = new Proxy(obj, {
get (target, prop, receiver) {
console.log('读取属性', prop)
return Reflect.get(target, prop, receiver)
}
})
console.log(proxy.a) // 控制台输出:读取属性 a,返回 1
Reflect
提供了更一致、可控的对象操作方式,常与 Proxy
配合实现自定义行为,是现代 JS 元编程的重要工具。