在 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);
假设我有一个 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);