插件窝 干货文章 Object.defineProperty和Proxy结合时,apply方法为何会执行两次?

Object.defineProperty和Proxy结合时,apply方法为何会执行两次?

Proxy apply defineProperty myFunction 134    来源:    2025-03-19

在JavaScript中,Object.definePropertyProxy 是两种不同的机制,用于拦截和自定义对象的行为。当你将它们结合使用时,可能会遇到一些意想不到的行为,比如 apply 方法执行两次的情况。

原因分析

  1. Object.defineProperty 的作用:

    • Object.defineProperty 用于在对象上定义一个新属性,或者修改一个已有属性。它可以用来定义属性的 gettersetter,从而在访问或修改属性时执行自定义逻辑。
  2. Proxy 的作用:

    • Proxy 用于创建一个对象的代理,从而可以拦截并自定义该对象的基本操作(如属性访问、赋值、枚举等)。Proxyapply 陷阱用于拦截函数的调用。
  3. 结合使用时的行为:

    • 当你同时使用 Object.definePropertyProxy 时,可能会发生以下情况:
      • 如果你在 Proxyapply 陷阱中调用了原始函数,而该函数又被 Object.defineProperty 拦截(例如通过 getter),那么 apply 陷阱可能会被触发两次。
      • 具体来说,第一次触发是因为 Proxy 拦截了函数调用,第二次触发是因为 Object.definePropertygetter 被调用,导致函数再次被调用。

示例代码

function myFunction() {
    console.log('Function executed');
}

const handler = {
    apply(target, thisArg, argumentsList) {
        console.log('Proxy apply trap');
        return Reflect.apply(target, thisArg, argumentsList);
    }
};

const proxy = new Proxy(myFunction, handler);

Object.defineProperty(window, 'myFunction', {
    get() {
        console.log('Getter called');
        return proxy;
    }
});

myFunction(); // 这里会触发两次 apply 陷阱

输出结果

Getter called
Proxy apply trap
Function executed
Proxy apply trap
Function executed

解释

  1. myFunction() 被调用时,首先会触发 Object.definePropertygetter,返回 proxy
  2. 然后 proxyapply 陷阱被触发,执行 Reflect.apply,调用原始函数 myFunction
  3. myFunction 执行过程中,如果再次访问 myFunctiongetter 会被再次触发,导致 apply 陷阱再次执行。

解决方案

为了避免 apply 方法执行两次,可以考虑以下方法:

  1. 避免在 getter 中返回 Proxy:

    • 如果可能,尽量避免在 Object.definePropertygetter 中返回 Proxy 对象。
  2. 使用标志位:

    • apply 陷阱中使用标志位来避免重复执行。
let isExecuting = false;

const handler = {
    apply(target, thisArg, argumentsList) {
        if (isExecuting) return;
        isExecuting = true;
        console.log('Proxy apply trap');
        const result = Reflect.apply(target, thisArg, argumentsList);
        isExecuting = false;
        return result;
    }
};

const proxy = new Proxy(myFunction, handler);

Object.defineProperty(window, 'myFunction', {
    get() {
        console.log('Getter called');
        return proxy;
    }
});

myFunction(); // 现在只会触发一次 apply 陷阱

输出结果

Getter called
Proxy apply trap
Function executed

通过这种方式,你可以确保 apply 陷阱只执行一次。