如何解决Backbone 旧版应用中的“这个”/范围问题,_.bind 无法解决
对于一个继承了 7 年历史的 Backbone 站点,我有一个视图“PaymentInfoForm”,我需要在其中实现一个新的小部件来处理在线支付。新的小部件(我们称之为 inceptionLib)基本上是第三方 UI 和第三方 API 的验证包装器。 inceptionLib 是全新的、专有的,并且没有很好的文档记录,所以它是一个相当不透明的黑匣子,但我可以让它在独立的演示 JavaScript 中正常工作。但是,尝试与 Backbone 集成给我带来了麻烦。
inceptionLib 要求我创建一个新函数 inceptionLibFieldListener,作为 PaymentInfoForm 视图的一部分,以在用户与小部件交互时处理状态更改。在该函数中,我需要做的是能够设置我的视图的几个属性。我在使用“this”关键字时遇到问题。我知道在内部函数中使用“this”时,我的视图会失去范围,但我不确定为什么会发生这种情况(除非它与黑匣子内的某些东西有关!)。如果我试图将我的函数包装在 _.bind 中作为该类型问题的常见解决方案,'this' 就变成了对 window 对象的引用——就好像它在我的对象模型中向上“跳过一个级别”一样。我不知道如何强制“this”成为我的视图——就像下一个函数一样——这样我就可以从这里修改我的视图属性。
感觉 inceptionLib 正在劫持“这个”,好吧,我真的需要它回来!任何帮助表示赞赏。
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,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,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
代码应该可以正常工作。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。