本文分析了Object.defineProperty
和Proxy
结合使用时,Proxy
的apply
方法被调用两次的现象。 以下代码片段展示了这个问题:
const test = { querySelector() {} }; Object.defineProperty(test, "querySelector", { get() { return new Proxy(document.querySelector, { apply(target, thisArgs, args) { console.log('test', thisArgs); return thisArgs.querySelector.apply(document, args); } }); } }); // 执行代码 test.querySelector("body");
运行此代码,控制台将输出两次"test",thisArgs
分别指向空对象{}
和#document
。这并非Proxy
本身的特性,而是Object.defineProperty
和Proxy
的组合以及this
上下文变化导致的。
关键在于thisArgs
。第一次调用test.querySelector("body")
时,Object.defineProperty
定义的get
方法被执行。该get
方法创建了一个Proxy
对象,其apply
方法立即被调用。此时,thisArgs
指向test
对象({}
),因此控制台第一次输出test {}
。
然而,get
方法返回的是thisArgs.querySelector.apply(document, args)
。由于thisArgs
指向test
,这等效于test.querySelector.apply(document, args)
。 因为test.querySelector
本身也是通过Object.defineProperty
定义的,所以调用它会再次触发get
方法,从而再次创建并执行Proxy
对象的apply
方法。这次,thisArgs
指向document
,所以控制台第二次输出test #document
。
因此,并非document.querySelector
自身递归调用导致二次执行,而是thisArgs
上下文的变化导致Object.defineProperty
定义的get
方法连续执行两次,从而触发了Proxy
的apply
方法两次。 thisArgs
在第一次apply
调用中指向自定义的空对象test
,在第二次apply
调用中指向document
对象,这就是导致两次输出的关键。 为了避免这个问题,建议重新设计代码逻辑,避免在get
方法中直接调用自身属性,或者使用不同的方法来实现所需的功能。 例如,可以考虑直接在apply
方法中处理逻辑,而不是再次调用querySelector
。