微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Backbone 旧版应用中的“这个”/范围问题,_.bind 无法解决

如何解决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 不会绑定 aMethodx。换句话说,当 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 实例。这是您可以在 contructorinitialize 方法中执行的操作,但如果您可能还想以未绑定形式使用该方法,我不建议这样做:

_.bindAll(this,'inceptionLibFieldListener');

使用后一种方法,您现有的 render 代码应该可以正常工作。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。