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

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

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

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

原因分析

  1. Object.defineProperty 的作用:

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

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

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

示例代码

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.defineProperty 的 getter,返回 proxy。
  2. 然后 proxy 的 apply 陷阱被触发,执行 Reflect.apply,调用原始函数 myFunction。
  3. 在 myFunction 执行过程中,如果再次访问 myFunction,getter 会被再次触发,导致 apply 陷阱再次执行。

解决方案

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

  1. 避免在 getter 中返回 Proxy:

    • 如果可能,尽量避免在 Object.defineProperty 的 getter 中返回 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 陷阱只执行一次。