深入解析:Object.defineProperty与Proxy结合引发的双重执行
本文将剖析Object.defineProperty和Proxy组合使用时,为何会触发两次执行的现象。以下代码片段将作为分析的基础:
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上下文。这正是我们试图解答的谜题:apply方法为何被调用两次?第一次thisArgs为何是{},第二次又是如何触发的?
关键在于thisArgs和get方法的返回值。
当第一次调用test.querySelector("body")时,Object.defineProperty定义的get方法被触发。在这个get方法内部,我们创建了一个Proxy对象,其apply方法记录了调用上下文thisArgs。此时,thisArgs指向test对象,即{},因此控制台第一次输出test {}。
然而,get方法的返回值是这个Proxy对象。 thisArgs.querySelector.apply(document, args)这行代码至关重要。它并非直接调用document.querySelector,而是通过thisArgs.querySelector(也就是test.querySelector)来调用。由于test.querySelector本身也是通过Object.defineProperty定义的,因此再次触发了get方法。
这次,thisArgs指向document对象,因为apply方法改变了上下文。所以控制台第二次输出test [object HTMLDocument]。document.querySelector.apply(document, args)最终完成了对querySelector方法的实际调用。
总结:
两次apply方法的调用并非因为document.querySelector内部再次调用自身,而是因为get方法的返回值是一个Proxy对象,并且在get方法内部使用了thisArgs.querySelector.apply,导致再次触发了get方法,从而产生了第二次apply调用。这突显了Object.defineProperty和Proxy结合使用时的复杂性,需要深入理解this上下文和apply方法的工作机制。 为了避免这种双重执行,建议重新设计代码逻辑,避免在get方法中再次调用自身属性。