我有一些代码要动态替换一个函数,并且我考虑过使用ms-dos中断(从C ++ / asm背景到JS)中使用的惯用法。所以我写了一个有效的代码片段,但是...如果该函数使用了'this'所引用的任何内容,则无效。如何使其与this-vars一起使用以及它是否也是原型函数。这个成语叫什么名字?
搜索“方法链接”是指另一种无关的非显着事物。
function patient(a,s,d) { /*do something*/ }
....
var oldFunc = patient;
patient = function(a,s,d) {
if(a==something) oldFunc(a,s,d); else { /* do something*/ }
}
答案 0 :(得分:2)
您可以使用Function#bind
将this
绑定到新功能。
function patient(a, s, d) { /*do something*/ }
// ....
var oldFunc = patient,
victim = function(a, s, d) {
if (a == something) oldFunc(a, s, d); else { /* do something*/ }
}.bind(this);
答案 1 :(得分:0)
如果您尝试覆盖某个函数但丢失了this
上下文,则解决此问题的方法很少。考虑下面的简单示例。
class Alarm {
setTime(time) {
this.time = time;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
toString() { return `time: ${this.time}\nenabled: ${this.enabled}`}
}
const myAlarm = new Alarm();
myAlarm.setTime("12:00").setEnabled(true);
console.log(myAlarm.toString());
在这种情况下,一切正常,因为this
从未被篡改,因此我们每次都希望这样做。如果我们尝试天真的覆盖setTime
方法,就会发生以下情况:
class Alarm {
setTime(time) {
this.time = time;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
toString() { return `time: ${this.time}\nenabled: ${this.enabled}`}
}
const myAlarm = new Alarm();
//let's override something
const oldSetTime = myAlarm.setTime;
myAlarm.setTime = function(time) {
console.log("overriden method!");
return oldSetTime(time); //this will lose the context of "this"
}
myAlarm.setTime("12:00").setEnabled(true);//error because "this" is undefined
console.log(myAlarm.toString());
因此,幼稚的方法行不通。有几种方法可以解决上下文丢失的问题。
Function#bind
绑定函数时,实际上创建了一个新的函数,其中this
上下文被永久设置为某种东西。它称为“绑定函数”。
class Alarm {
setTime(time) {
this.time = time;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
toString() { return `time: ${this.time}\nenabled: ${this.enabled}`}
}
const myAlarm = new Alarm();
const oldSetTime = myAlarm.setTime.bind(myAlarm); //bind a function to a context permanently
myAlarm.setTime = function(time) {
console.log("overriden method!");
return oldSetTime(time);
}
myAlarm.setTime("12:00").setEnabled(true);
console.log(myAlarm.toString());
Function#apply
或Function#call
两者非常相似。在这两种情况下,您都将执行一个函数并提供this
上下文的值。然后,您可以向函数提供任何其他参数来执行。 .call()
将只接受任意数量的参数并将其转发,而.apply()
仅需要一个一个参数,该参数类似于数组,并将被转换为arguments
对于执行的功能。
class Alarm {
setTime(time) {
this.time = time;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
toString() { return `time: ${this.time}\nenabled: ${this.enabled}`}
}
const myAlarm = new Alarm();
const oldSetTime = myAlarm.setTime;
myAlarm.setTime = function(time) {
console.log("overriden method!");
return oldSetTime.call(this, time);
}
myAlarm.setTime("12:00").setEnabled(true);
console.log(myAlarm.toString());
class Alarm {
setTime(time) {
this.time = time;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
toString() { return `time: ${this.time}\nenabled: ${this.enabled}`}
}
const myAlarm = new Alarm();
const oldSetTime = myAlarm.setTime;
myAlarm.setTime = function() {
console.log("overriden method!");
return oldSetTime.apply(this, arguments);
}
myAlarm.setTime("12:00").setEnabled(true);
console.log(myAlarm.toString());
.apply()
方法通常更具扩展性,因为您只需转发最初执行的arguments
。这样,如果原始函数更改了签名,那么您实际上并不在乎,也不需要更改任何内容。假设现在是setTime(hours, minutes)
-转发到原始版本仍然有效。如果您使用.call()
,则需要做更多的工作-您需要去更改传递的参数,并且需要将整个替代项修改为类似的
myAlarm.setTime = function(hours, minutes) {//you need to know what the function takes
console.log("overriden method!");
return oldSetTime.call(this, hours, minutes); //so you can pass them forward
}
尽管您可以使用spread syntax
来解决此问题myAlarm.setTime = function() {//ignore whatever is passed in
console.log("overriden method!");
return oldSetTime.call(this, ...arguments); //spread the arguments
}
在这种情况下,.apply(this, arguments)
和.call(this, ...arguments)
的结果都相同,但需要进行少量的计划。
您可以设置一个代理来侦听并可能修改呼叫,而不是修改对象。在某些情况下,或者只是您所需要的,这可能是一个过大的杀伤力。这是一个覆盖所有方法调用的示例实现
class Alarm {
setTime(time) {
this.time = time;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
toString() { return `time: ${this.time}\nenabled: ${this.enabled}`}
}
const allMethodsHandler = {
get(target, propKey) {
const origMethod = target[propKey];
return function() {
const result = origMethod.apply(target, arguments); //you can also use .call(target, ...arguments)
console.log(`called overriden method ${propKey}`);
return result;
};
}
};
const myAlarm = new Alarm();
myOverridenAlarm = new Proxy(myAlarm, allMethodsHandler);
myOverridenAlarm
.setTime("12:00")
.setEnabled(true); //you get no log!
console.log(myOverridenAlarm.toString());
但是,必须小心。如您所见,对setEnabled
的调用不会产生日志。这是因为它不通过代理-setTime
返回原始对象而不是代理。我将其留给我们展示一个问题。有时,全部覆盖功能太强大了。在这种情况下,如果您想获取myOverridenAlarm.time
,就会出现问题,例如,它仍将通过处理程序并将其视为方法。您可以修改处理程序以检查方法,甚至可以检查结果是否为相同的对象(流体接口)并将其包装在代理中,或者适当地返回当前代理,但这样做有点麻烦。这也取决于您的用例。
更简单的方法是通过代理覆盖单个方法。与使用.bind
或.call
或.apply
的概念非常相似,但在某些方面更可重用。
class Alarm {
setTime(time) {
this.time = time;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
toString() { return `time: ${this.time}\nenabled: ${this.enabled}`}
}
const singleMethodHandler = {
apply(targetMethod, thisArg, ...args) { //collect the rest of the arguments into "args" to pass on
console.log(`overriden method!`);
const result = targetMethod.apply(thisArg, args);
return result;
}
};
const myAlarm = new Alarm();
//override setTime with a proxied version
myAlarm.setTime = new Proxy(myAlarm.setTime, singleMethodHandler);
myAlarm.setTime("12:00").setEnabled(true);
console.log(myAlarm.toString());
这是一个更轻量的版本,因为您不会覆盖当前和将来的所有方法,因此它更易于管理。而且,它是可重用的-您只需添加myAlarm.setEnabled = new Proxy(myAlarm.setEnabled, singleMethodHandler);
,您将在其中获得相同的功能。因此,如果您只需要有选择地重写具有相同功能的方法(在本例中为日志记录),那么这很容易做到。但是,这确实意味着要更改对象。
如果您想避免更改 instance ,并且希望在所有实例上应用相同的内容,则可以更改对象的 prototype ,以便对该方法将使用代理版本:
class Alarm {
setTime(time) {
this.time = time;
return this;
}
setEnabled(enabled) {
this.enabled = enabled;
return this;
}
toString() { return `time: ${this.time}\nenabled: ${this.enabled}`}
}
const singleMethodHandler = {
apply(targetMethod, thisArg, ...args) { //collect the rest of the arguments into "args" to pass on
console.log(`overriden method called with: "${args}"`);
const result = targetMethod.apply(thisArg, args);
return result;
}
};
//changing prototype before making a new isntance
Alarm.prototype.setTime = new Proxy(Alarm.prototype.setTime, singleMethodHandler);
const myAlarm = new Alarm();
//changing the prototype after making a new instance
Alarm.prototype.setEnabled = new Proxy(Alarm.prototype.setEnabled, singleMethodHandler);
myAlarm.setTime("12:00").setEnabled(true); //we get logs both times
console.log(myAlarm.toString());