在 JavaScript 中,特别是 NodeJS 上的 ES6,我可以在运行时直接操作 class 的 getter 和 setter 函数吗?

In JavaScript, specifically ES6 on NodeJS, can I manipulate getter and setter functions of a class directly at runtime?

假设我有一个 class 定义如下:

class MyClass {
    constructor(a, b) {
        this._a = a;
        this._b = b;
    }

    get a() {
        return this._a;
    }

    set a(val) {
        this._a = val;
    }

    add() {
        return this._a + this._b;
    }
}

我希望能够在运行时直接访问和操作 getter 和 setter 函数,以便将它们包装在额外的调试代码中。使用 'add' 函数,我可以这样做:

let oldAdd = MyClass.prototype.add;
MyClass.prototype.add = function() {
    console.log('add called!');
    let result = oldAdd.call(this);
    console.log('add result: ' + result);
    return result;
}

但是,我找不到以类似方式修改 getter 和 setter 函数的方法。

我试过了

let propDef = Reflect.getOwnPropertyDescriptor(MyClass.prototype, 'a');
propDef.get = function() { 
    // ...
}

但实际上并没有应用此更改。

有什么想法吗?

我也很想知道是否可以用同样的方式访问和修改构造函数。

是的,您可以通过重新配置属性来实现。这是一个例子(见评论):

class MyClass {
    constructor(a, b) {
        this._a = a;
        this._b = b;
    }

    get a() {
        return this._a;
    }

    set a(val) {
        this._a = val;
    }

    add() {
        return this._a + this._b;
    }
}

// Use it before redefining
const instance = new MyClass(1, 2);
console.log(instance.a); // 1
instance.a = 2;
console.log(instance.a); // 2

// Redefine the property
const desc = Reflect.getOwnPropertyDescriptor(MyClass.prototype, "a");
const {get: originalGet, set: originalSet} = desc;
desc.get = function() {
  const value = originalGet.call(this);
  console.log("Debugging 'get' here, a's value is " + value);
  return value;
};
desc.set = function(newValue) {
  console.log("Debugging 'set' here, a's new value is " + newValue);
  originalSet.call(this, newValue);
};
Object.defineProperty(MyClass.prototype, "a", desc);

// Use it after redefining
console.log(instance.a); // 2, after seeing console statement
instance.a = 3;          // Triggers another console statement
console.log(instance.a); // 3 (w/ console statement)

你所做的没有工作的部分原因是你从未在对象上设置新的描述符。您返回的描述符不提供对对象内定义的直接访问,它只是 getOwnPropertyDescriptor 创建的一个新对象。要使更改生效,您需要设置新的描述符。


您在下面询问了有关对 MyClass 本身执行相同操作的问题。正如您所指出的,除了替换函数外,我们还需要确保它的属性显示在替换函数中。

一个简单易行的方法是让新函数继承旧函数:

const originalConstructor = MyClass;
MyClass = class MyClass extends originalConstructor {
    constructor(...args) {
        return new originalConstructor(...args);
    }
};

这是一个 little-known 事实,当 B 扩展 A 时,会发生 两个 事情:

  • B.prototype的原型设置为A.prototype(这是大家熟知的),而
  • B的原型设置为A(减去well-known)

因此 A 上的任何静态信息都可以通过继承在 B 上使用。

所以:

class Base {
  feature() {}
  static staticFeature() {}
}
class MyClass extends Base {
  subFeature() {}
  static staticSubFeature() {}
}
const originalConstructor = MyClass;
MyClass = class MyClass extends originalConstructor {
  constructor(...args) {
    return new originalConstructor(...args);
  }
};

console.log(typeof MyClass.staticFeature);
console.log(typeof MyClass.staticSubFeature);
const instance = new MyClass();
console.log(typeof instance.feature);
console.log(typeof instance.subFeature);

或者如果你想要一个真正的 high-fidelity 副本,你可以从 MyClass 的原型派生,然后复制任何 MyClass 自己的属性;但它更复杂:

const originalConstructor = MyClass;
MyClass = class MyClass extends Object.getPrototypeOf(originalConstructor) {
  constructor(...args) {
    return new originalConstructor(...args);
  }
};
Object.getOwnPropertyNames(originalConstructor)
  .concat(Object.getOwnPropertySymbols(originalConstructor))
  .forEach(name => {
    MyClass[name] = originalConstructor[name];
  });

示例:

class Base {
  feature() {}
  static staticFeature() {}
}
class MyClass extends Base {
  subFeature() {}
  static staticSubFeature() {}
}
const originalConstructor = MyClass;
MyClass = class MyClass extends Object.getPrototypeOf(originalConstructor) {
  constructor(...args) {
    return new originalConstructor(...args);
  }
};
Object.getOwnPropertyNames(originalConstructor)
  .concat(Object.getOwnPropertySymbols(originalConstructor))
  .forEach(name => {
    MyClass[name] = originalConstructor[name];
  });

console.log(typeof MyClass.staticFeature);
console.log(typeof MyClass.staticSubFeature);
const instance = new MyClass();
console.log(typeof instance.feature);
console.log(typeof instance.subFeature);