研究个问题 下面前端代码为啥成立 Object.prototype[Symbol.iterator] = function() {
return Object.values(this)Symbol.iterator
}
let [a, b] = {a: 1, b: 2}
console.log(a, b); // 1 2
这段前端代码之所以能让 let [a, b] = {a: 1, b: 2}
正常工作,并输出 1 2
,是因为它巧妙地利用了 JavaScript 中的迭代器协议(Iterator Protocol)和原型链(Prototype Chain) 。
我们来一步步拆解:
1. 正常情况下,为什么 let [a, b] = {a: 1, b: 2}
会报错?
在 JavaScript 中,let [a, b] = ...
这种语法叫做数组解构赋值(Array Destructuring Assignment) 。它要求等号右边的值必须是可迭代的(Iterable) 。
什么是可迭代的?一个对象如果实现了迭代器协议,那么它就是可迭代的。迭代器协议的核心是对象必须有一个属性名为 Symbol.iterator
的方法。这个方法返回一个迭代器(Iterator) 。迭代器是一个对象,它有一个 next()
方法,每次调用 next()
都会返回一个 { value: any, done: boolean }
形式的对象,直到 done
为 true
。
常见的可迭代对象包括:数组(Array
)、字符串(String
)、Map、Set、arguments 对象、NodeList 等。
而普通的 JavaScript 对象(如 {a: 1, b: 2}
)默认不是可迭代的 。所以,如果你直接运行 let [a, b] = {a: 1, b: 2}
,通常会抛出 TypeError: {a: 1, b: 2} is not iterable
这样的错误。
2. 代码 Object.prototype[Symbol.iterator] = function() { return Object.values(this)[Symbol.iterator]() }
的作用
这行代码是关键!它做了以下几件事:
-
Object.prototype
: 这是所有普通 JavaScript 对象的原型。当你创建一个对象字面量{}
时,它的原型就是Object.prototype
。这意味着,任何继承自Object.prototype
的对象(几乎所有你创建的普通对象都如此)都会拥有你在这里添加的属性和方法。 -
[Symbol.iterator]
: 这是 JavaScript 中的一个知名 Symbol 。当 JavaScript 引擎需要对一个对象进行迭代时(例如在for...of
循环中,或者进行数组解构时),它就会查找并调用这个Symbol.iterator
方法。 -
function() { ... }
: 这就是我们为Object.prototype
添加的Symbol.iterator
方法的实现。this
: 在这个方法内部,this
指向当前正在被迭代的对象。例如,在let [a, b] = {a: 1, b: 2}
的上下文中,this
就是{a: 1, b: 2}
这个对象。Object.values(this)
:Object.values()
方法会返回一个给定对象自身的所有可枚举属性值的数组。对于{a: 1, b: 2}
,Object.values(this)
将返回数组 ``。[Symbol.iterator]()
: 数组是天生可迭代的。[Symbol.iterator]()
会返回数组 `` 的默认迭代器。这个迭代器会按顺序(先1
,再2
)产生值。
所以,这整行代码的含义是:
我们修改了 Object
的原型,给所有普通对象添加了一个 Symbol.iterator
方法。当任何普通对象被要求迭代时,它会先把自己所有属性的值提取出来,形成一个数组,然后返回这个数组的默认迭代器。
3. let [a, b] = {a: 1, b: 2}
如何工作?
当 JavaScript 引擎执行 let [a, b] = {a: 1, b: 2}
这行代码时:
-
它发现等号右边是一个普通对象
{a: 1, b: 2}
。 -
由于是数组解构赋值,引擎会尝试获取这个对象的迭代器。
-
它沿着
{a: 1, b: 2}
的原型链向上查找Symbol.iterator
方法,最终在Object.prototype
上找到了我们添加的那个方法。 -
引擎调用这个方法,此时
this
就是{a: 1, b: 2}
。 -
方法内部执行
Object.values(this)
,得到数组 ``。 -
然后执行
[Symbol.iterator]()
,返回了数组 `` 的迭代器。 -
数组解构
[a, b]
开始使用这个迭代器:- 第一次调用迭代器的
next()
,得到{ value: 1, done: false }
,于是a
被赋值为1
。 - 第二次调用迭代器的
next()
,得到{ value: 2, done: false }
,于是b
被赋值为2
。 - 后续再调用
next()
,done
将为true
,表示迭代结束。
- 第一次调用迭代器的
总结
通过修改 Object.prototype
,我们强制让所有普通对象变得"可迭代",并且它们的迭代行为被定义为"按顺序迭代其所有属性的值"。这就是为什么 let [a, b] = {a: 1, b: 2}
能够成功将 1
赋值给 a
,将 2
赋值给 b
的原因。
重要提示:
尽管这段代码能实现你看到的效果,但在实际生产环境中,强烈不建议 直接修改 Object.prototype
。因为 Object.prototype
是所有对象的基石,对其进行修改会影响到程序中所有的对象,可能导致:
- 命名冲突: 与其他库或框架添加的同名方法冲突。
- 意外行为: 改变了 JavaScript 核心对象的默认行为,可能导致难以调试的问题。
- 性能问题: 某些优化可能因为原型链的改变而失效。
这种做法更多是为了演示 JavaScript 迭代器协议的工作原理,而非推荐的实践。