深入剖析Object.defineproperty與proxy結合引發(fā)的雙重apply調用
本文將深入分析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); } }); } }); // 執(zhí)行代碼 test.querySelector("body");
運行這段代碼,控制臺將打印兩次“test”,分別對應不同的thisArgs上下文。這是因為thisArgs的指向變化以及apply方法的調用機制。
第一次apply調用: 當執(zhí)行test.querySelector(“body”)時,test.querySelector的getter方法被觸發(fā),返回一個Proxy對象。這個Proxy對象的apply方法立即被調用,此時thisArgs指向test對象,也就是一個空對象{}。
關鍵的第二次回調: 在Proxy的apply方法內部,我們調用了thisArgs.querySelector.apply(document, args)。由于thisArgs指向test,這行代碼等價于test.querySelector.apply(document, args)。 這行代碼再次觸發(fā)了test.querySelector的getter方法,因為test.querySelector是通過Object.defineProperty定義的,并再次返回一個新的Proxy對象。這個新的Proxy對象的apply方法隨后被調用,這次thisArgs指向document對象,因為apply方法將上下文切換到了document。
因此,兩次apply方法調用的thisArgs分別指向{}和document,導致控制臺輸出兩次“test”,并分別對應不同的上下文。 這并非document.querySelector自身遞歸調用導致的,而是由于thisArgs的指向變化和Object.defineProperty的getter方法被兩次觸發(fā)所致。 問題的根源在于在Proxy的apply方法內部再次訪問了test.querySelector,從而再次觸發(fā)了getter,造成了無限遞歸的可能性(此處因為test.querySelector是一個空函數(shù),避免了無限遞歸)。
為了避免這個問題,應該直接使用target,而不是thisArgs.querySelector:
const test = { querySelector() {} }; Object.defineProperty(test, "querySelector", { get() { return new Proxy(document.querySelector, { apply(target, thisArgs, args) { console.log('test', thisArgs); return target.apply(document, args); // 使用 target 代替 thisArgs.querySelector } }); } }); test.querySelector("body");
修改后的代碼只調用apply一次,解決了雙重調用的問題。 這說明了在Proxy的handler方法中,正確理解target和thisArgs的含義,并謹慎使用,對于避免意外行為至關重要。