'this'/Backbone 遗留应用程序中的范围问题,_.bind 无法解决
'this'/scope problem in Backbone legacy app, that _.bind isn't solving
对于一个已有 7 年历史的 Backbone 网站,我有一个视图“PaymentInfoForm”,我需要在其中实现一个新的小部件来处理在线支付。新的小部件(我们称之为 inceptionLib)基本上是第三方 UI 和第四方 API 的验证包装器。 inceptionLib 是全新的、专有的,并且没有很好的文档记录,所以它是一个相当不透明的黑盒子,但我可以让它在独立演示中正常工作 JavaScript。但是,尝试与 Backbone 集成给我带来了麻烦。
inceptionLib 要求我创建一个新函数 inceptionLibFieldListener 作为我的 PaymentInfoForm 视图的一部分,以在用户与小部件交互时处理状态变化。在该函数中,我需要做的是能够设置我的视图的几个属性。 'this' 关键字有问题。我知道 'this' 在内部函数中使用时会失去我的视图范围,但我不确定为什么会发生在这里(除非它与黑匣子内的某些东西有关!)。如果我试图将我的函数包装在 _.bind 中作为此类问题的常见解决方案,'this' 只会成为对 window 对象的引用——就好像它“跳过了一个级别”在我的对象模型中向上。我不知道如何强制 'this' 成为我的视图——就像下一个函数一样——这样我就可以从这里修改我的视图属性。
感觉 inceptionLib 正在劫持 'this' 而且,好吧,我真的需要它回来!任何帮助表示赞赏。
App.PaymentInfoForm = Backbone.View.extend({ // existing view
oldProperty1: null, // existing property
myNewProperty2: null, // new property that I need to modify from inceptionLibFieldListener()
initialize: function (){...}, // create objects & stuff, doesn't matter
render: function(){ // render, including putting the inceptionLib widget on the page
{...}
var inceptionLibForm = inceptionLib.createForm();
var that = this;
vgsForm.initInceptionLib().then(function(){
inceptionLibForm.renderCreditCard('#cc-container', that.inceptionLibFieldListener);
inceptionLibForm.renderExpirationDate('#exp-container', that.inceptionLibFieldListener);
})
return this;
},
inceptionLibFieldListener: function(newState, prevState, flags) { // inceptionLib listener function
console.log(this); // result is output for the inceptionLib object
},
// if I change the inceptionLibFieldListener to this:
inceptionLibFieldListener: _.bind(function(newState, prevState, flags) { // _.bind should fix it
console.log(this); // but result is Window object
// }, this),
somethingElse: function(){ // unrelated function just to test the difference in 'this'
console.log(this); // result is console output for the view, as expected
},
...
您的问题来自对 this
工作原理的误解。 this
我就不详细解释了,因为MDN上有一篇good, comprehensive article。但是,我可以简短地解释为什么您的代码不起作用以及如何修复它。
当你做的时候
var that = this;
vgsForm.initInceptionLib().then(function(){
inceptionLibForm.renderCreditCard('#cc-container', that.inceptionLibFieldListener);
inceptionLibForm.renderExpirationDate('#exp-container', that.inceptionLibFieldListener);
})
that
确保 inceptionLibFieldListener
是从 PaymentInfoForm
实例中获得的,这是您试图实现的目标的一半。但是,执行 x.aMethod
不会 将 aMethod
绑定到 x
。换句话说,当 aMethod
运行时, this
仍然可以是任何东西。以下代码演示了这一点:
var x = {
aMethod: function() {
console.log(this.aProperty);
},
aProperty: 1
};
var y = {
aProperty: 2
};
var aMethod = x.aMethod; // correct function but not bound
aMethod(); // prints undefined
aMethod.call(y); // prints 2
x.aMethod(); // prints 1
您可能想知道为什么 aMethod
确实 似乎绑定到最后一个示例中的 x
。为什么 x.aMethod()
有效但 aMethod = x.aMethod; aMethod()
无效?为什么你不需要总是调用 aMethod.call(x)
?这是因为 JavaScript 引擎将 x.aMethod(a, b, c)
识别为特殊情况并自动将其转换为 x.aMethod.call(x, a, b, c)
。你可能会觉得这有悖常理,而且你说得有道理,但这正是 JS 的工作方式。
当你做的时候
inceptionLibFieldListener: _.bind(function(newState, prevState, flags) { // _.bind should fix it
console.log(this); // but result is Window object
}, this)
问题是 this
是在任何函数上下文之外计算的。 this
在全局范围内总是 window
对象,或者在 ES6 模块范围内是 undefined
。所以 _.bind
正在做它应该做的事情,但你传递的是错误的 this
.
您可以采取两种措施来解决此问题。第一个选项是在更合适的时候使用 _.bind
,当 this
确实具有您需要的值时,例如在您的 render
方法中:
var boundInceptionFieldListener = _.bind(this.inceptionLibFieldListener, this);
vgsForm.initInceptionLib().then(function(){
inceptionLibForm.renderCreditCard('#cc-container', boundInceptionFieldListener);
inceptionLibForm.renderExpirationDate('#exp-container', boundInceptionFieldListener);
})
另一种选择是使用 _.bindAll
,它会永久更改方法以始终绑定到您的 PaymentInfoForm
实例。这是您可以在 contructor
或 initialize
方法中执行的操作,但如果您可能还想以未绑定形式使用该方法,我不推荐这样做:
_.bindAll(this, 'inceptionLibFieldListener');
使用后一种方法,您现有的 render
代码应该按原样工作。
对于一个已有 7 年历史的 Backbone 网站,我有一个视图“PaymentInfoForm”,我需要在其中实现一个新的小部件来处理在线支付。新的小部件(我们称之为 inceptionLib)基本上是第三方 UI 和第四方 API 的验证包装器。 inceptionLib 是全新的、专有的,并且没有很好的文档记录,所以它是一个相当不透明的黑盒子,但我可以让它在独立演示中正常工作 JavaScript。但是,尝试与 Backbone 集成给我带来了麻烦。
inceptionLib 要求我创建一个新函数 inceptionLibFieldListener 作为我的 PaymentInfoForm 视图的一部分,以在用户与小部件交互时处理状态变化。在该函数中,我需要做的是能够设置我的视图的几个属性。 'this' 关键字有问题。我知道 'this' 在内部函数中使用时会失去我的视图范围,但我不确定为什么会发生在这里(除非它与黑匣子内的某些东西有关!)。如果我试图将我的函数包装在 _.bind 中作为此类问题的常见解决方案,'this' 只会成为对 window 对象的引用——就好像它“跳过了一个级别”在我的对象模型中向上。我不知道如何强制 'this' 成为我的视图——就像下一个函数一样——这样我就可以从这里修改我的视图属性。
感觉 inceptionLib 正在劫持 'this' 而且,好吧,我真的需要它回来!任何帮助表示赞赏。
App.PaymentInfoForm = Backbone.View.extend({ // existing view
oldProperty1: null, // existing property
myNewProperty2: null, // new property that I need to modify from inceptionLibFieldListener()
initialize: function (){...}, // create objects & stuff, doesn't matter
render: function(){ // render, including putting the inceptionLib widget on the page
{...}
var inceptionLibForm = inceptionLib.createForm();
var that = this;
vgsForm.initInceptionLib().then(function(){
inceptionLibForm.renderCreditCard('#cc-container', that.inceptionLibFieldListener);
inceptionLibForm.renderExpirationDate('#exp-container', that.inceptionLibFieldListener);
})
return this;
},
inceptionLibFieldListener: function(newState, prevState, flags) { // inceptionLib listener function
console.log(this); // result is output for the inceptionLib object
},
// if I change the inceptionLibFieldListener to this:
inceptionLibFieldListener: _.bind(function(newState, prevState, flags) { // _.bind should fix it
console.log(this); // but result is Window object
// }, this),
somethingElse: function(){ // unrelated function just to test the difference in 'this'
console.log(this); // result is console output for the view, as expected
},
...
您的问题来自对 this
工作原理的误解。 this
我就不详细解释了,因为MDN上有一篇good, comprehensive article。但是,我可以简短地解释为什么您的代码不起作用以及如何修复它。
当你做的时候
var that = this;
vgsForm.initInceptionLib().then(function(){
inceptionLibForm.renderCreditCard('#cc-container', that.inceptionLibFieldListener);
inceptionLibForm.renderExpirationDate('#exp-container', that.inceptionLibFieldListener);
})
that
确保 inceptionLibFieldListener
是从 PaymentInfoForm
实例中获得的,这是您试图实现的目标的一半。但是,执行 x.aMethod
不会 将 aMethod
绑定到 x
。换句话说,当 aMethod
运行时, this
仍然可以是任何东西。以下代码演示了这一点:
var x = {
aMethod: function() {
console.log(this.aProperty);
},
aProperty: 1
};
var y = {
aProperty: 2
};
var aMethod = x.aMethod; // correct function but not bound
aMethod(); // prints undefined
aMethod.call(y); // prints 2
x.aMethod(); // prints 1
您可能想知道为什么 aMethod
确实 似乎绑定到最后一个示例中的 x
。为什么 x.aMethod()
有效但 aMethod = x.aMethod; aMethod()
无效?为什么你不需要总是调用 aMethod.call(x)
?这是因为 JavaScript 引擎将 x.aMethod(a, b, c)
识别为特殊情况并自动将其转换为 x.aMethod.call(x, a, b, c)
。你可能会觉得这有悖常理,而且你说得有道理,但这正是 JS 的工作方式。
当你做的时候
inceptionLibFieldListener: _.bind(function(newState, prevState, flags) { // _.bind should fix it
console.log(this); // but result is Window object
}, this)
问题是 this
是在任何函数上下文之外计算的。 this
在全局范围内总是 window
对象,或者在 ES6 模块范围内是 undefined
。所以 _.bind
正在做它应该做的事情,但你传递的是错误的 this
.
您可以采取两种措施来解决此问题。第一个选项是在更合适的时候使用 _.bind
,当 this
确实具有您需要的值时,例如在您的 render
方法中:
var boundInceptionFieldListener = _.bind(this.inceptionLibFieldListener, this);
vgsForm.initInceptionLib().then(function(){
inceptionLibForm.renderCreditCard('#cc-container', boundInceptionFieldListener);
inceptionLibForm.renderExpirationDate('#exp-container', boundInceptionFieldListener);
})
另一种选择是使用 _.bindAll
,它会永久更改方法以始终绑定到您的 PaymentInfoForm
实例。这是您可以在 contructor
或 initialize
方法中执行的操作,但如果您可能还想以未绑定形式使用该方法,我不推荐这样做:
_.bindAll(this, 'inceptionLibFieldListener');
使用后一种方法,您现有的 render
代码应该按原样工作。