
本文介绍如何使用 es6 proxy 创建一个始终返回自身且支持 for...of 迭代的代理数组,关键在于正确拦截 symbol.iterator 等符号属性,确保代理对象满足可迭代协议。
在 JavaScript 中,for...of 循环依赖对象是否实现了可迭代协议(iterable protocol),即对象必须拥有 Symbol.iterator 方法并返回一个迭代器(iterator)。当对 Proxy 对象执行 for (let v of p) 时,引擎会尝试读取 p[Symbol.iterator] —— 这是一个 Symbol 类型的 key,而你的原始代码中仅显式处理了 'length' 和 'push' 字符串属性,却忽略了所有 symbol 属性(包括 Symbol.iterator),导致返回值为 p(即代理本身),而非一个合法的迭代器函数,最终抛出 TypeError: p is not iterable。
要解决该问题,核心是:在 get 拦截器中放行所有 symbol 类型的属性访问,让底层数组的原生行为(如 Array.prototype[Symbol.iterator])正常生效。同时,为保证代理数组首次访问即包含自身,应在 get 中优先完成初始化(obj.push(p)),而非操作 p.push(p)(后者可能引发无限递归或代理状态混乱)。
以下是完整、健壮的实现:
const p = new Proxy([], {
get(obj, prop) {
// 首次访问任意属性时,向底层数组推入自身(确保 self-reference)
if (obj.length === 0) {
obj.push(p);
}
// 放行 length、所有 symbol 属性(含 Symbol.iterator)、以及常用数组方法
if (prop === 'length' || typeof prop === 'symbol' ||
['push', 'pop', 'shift', 'unshift', 'forEach', 'map', 'filter', 'reduce', 'slice', 'concat'].includes(prop)) {
return obj[prop];
}
// 其他属性访问均返回代理自身(实现链式自引用)
return p;
}
});✅ 此实现在以下场景均能正确工作:
- for (let v of p) { console.log(v); } → 输出 [Proxy](即 p 自身);
- p.any.any.any → 无限链式代理,每一层都是同一 p;
- p.map(x => x)、p.forEach(console.log) → 基于原生数组方法正常执行;
- p.length → 返回 1;
- Array.isArray(p) → 返回 true(Proxy 不改变目标对象类型)。
⚠️ 注意事项:
- 不要使用 p.push(...) 初始化,应操作 obj.push(...),避免触发代理自身的 get 逻辑造成循环;
- 若需严格模拟“只读”或“不可扩展”行为,可配合 set、defineProperty、preventExtensions 等拦截器进一步约束;
- 生产环境慎用深度自引用结构,可能影响调试、序列化(如 JSON.stringify(p) 会报错)及内存回收。
通过精准控制 symbol 属性的透传,你就能让 Proxy 同时满足数组语义与可迭代协议——这是构建高级数据映射、Mock 工具或领域特定语法糖的关键基础能力。










