_.bind无法解决的Backbone旧应用程序中的'this'/scope问题

mzillmmw  于 2022-11-24  发布在  其他
关注(0)|答案(1)|浏览(113)

对于一个继承的已有7年历史的Backbone站点,我有一个视图“PaymentInfoForm”,需要在其中实现一个新的小部件来处理在线支付。(让我们称之为inceptionLib)基本上是第三方API第三方UI和验证 Package 器。inceptionLib是全新的、专有的,而且没有很好的文档记录,因此它是一个相当不透明的黑盒子,但是我可以让它在独立的演示JavaScript中工作得很好。但是,试图与Backbone集成给我带来了麻烦。
inceptionLib要求我创建一个新的函数inceptionLibFieldListener,作为PaymentInfoForm视图的一部分,以处理用户与小部件交互时的状态变化。在该函数中,我需要做的是能够设置视图的几个属性。我在使用“this”关键字时遇到了问题。我知道,当在内部函数中使用“this”时,它会失去视图的作用域。但我不知道为什么会这样如果我试图将函数 Package 在_.bind中作为这类问题的通用解决方案,'this'只是成为对窗口对象的引用-就像它在我的对象模型中向上“跳过一级”一样。我不知道如何强制'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, 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
    },
    
    ...
jtoj6r0c

jtoj6r0c1#

您的问题是由于对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示例。这是您可以在contructorinitialize方法中执行的操作,尽管如果您还想以未绑定的形式使用该方法,我不推荐这样做:

_.bindAll(this, 'inceptionLibFieldListener');

使用后一种方法,现有的render代码应该可以按原样工作。

相关问题