0

考虑代码(也在 JSFiddle 上):

// Backbone calls this "view3"
InnerView = Backbone.View.extend({

    initialize: function() {
        this.parent = this.options.parent;
        this.listenTo(
            this.model,
            "change:name",
            function() { this.parent.onNameChange() }
        );
    }


});

// Backbone calls this "view2"
OuterView = Backbone.View.extend({
    initialize: function() {
        this.innerView = new InnerView({
            model: this.model,
            parent: this
        });
    },

    onNameChange: function() {
        console.log("'this' is bound to the outerview instance: " + (this.cid === "view2"));
    }
});


var myModel = new Backbone.Model({ name: "foo" });
var outerView = new OuterView({ model: myModel });

// trigger onNameChange
myModel.set("name", "bar");

这将打印'this' is bound to the outerview instance: true到控制台。但是,如果我将回调更改为:

this.listenTo(
    this.model,
    "change:name",
    this.parent.onNameChange
);

(我在这个小提琴中完成了),然后我的控制台显示'this' is bound to the outerview instance: false. 似乎它this是绑定到InnerView实例的。

为什么是这样?阅读listenTo 文档后,我希望this始终绑定到一个InnerView实例,因为listenTo在内部调用InnerView

4

2 回答 2

2

这将是正确的行为。在第一个示例中,this 指的是“this.parent”,而第二个示例指的是“this”。

第一个例子

this.listenTo(
       this.model,
       "change:name",
        function() { this.parent.onNameChange() }
);

onNameChange: function() {
    // here this refers to "this.parent"
}

第二个例子

this.listenTo(
    this.model,
    "change:name",
    this.parent.onNameChange // Reference to onNameChange detached from "parent"
);

onNameChange: function() {
    // here this refers to "this" and not "this.parent"
}

解决方案

如果您想使用第二个示例中的代码,您可以进行以下更改之一。

onNameChange: function() {
    console.log("'this' is ... instance: " + (this.parent.cid === "view2"));
}

或者

this.listenTo(
    this.model,
    "change:name",
    $.proxy(this.parent.onNameChange, this.parent)
);
于 2013-10-16T12:01:02.390 回答
1

实际上,这是 Backbone 的设计行为。首先让我们看看 Backbone 是如何实现的listenTo

  var listenMethods = {listenTo: 'on', listenToOnce: 'once'};

  // Inversion-of-control versions of `on` and `once`. Tell *this* object to
  // listen to an event in another object ... keeping track of what it's
  // listening to.
  _.each(listenMethods, function(implementation, method) {
    Events[method] = function(obj, name, callback) {
      var listeningTo = this._listeningTo || (this._listeningTo = {});
      var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
      listeningTo[id] = obj;
      if (!callback && typeof name === 'object') callback = this;
      obj[implementation](name, callback, this);
      return this;
    };
  });

obj[implementation](name, callback, this);是魔法发生的地方。当你

this.listenTo(
    this.model,
    "change:name",
    this.parent.onNameChange
);

Backbone 实际上添加了一个on事件监听器到this.model, obj[implementation](name, callback, this);-> this.model['on']('change:name', this.parent.onNameChange, this);

第三个参数obj[implementation](name, callback, this);实际上是context由 Backbone 调用的,触发事件时会传回给你的回调。

骨干网的on实现。

on: function(name, callback, context) {
  if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
  this._events || (this._events = {});
  var events = this._events[name] || (this._events[name] = []);
  events.push({callback: callback, context: context, ctx: context || this});
  return this;
},

这是它如何触发事件

var triggerEvents = function(events, args) {
    var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
    switch (args.length) {
      case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
      case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
      case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
      case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
      default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
    }
  };

看看那个ev.ctx变量,这就是this你的回调中将引用的变量。

因此,如果回调是this.parent.onNameChangethisinonNameChange将绑定到错误的对象。

于 2013-10-16T12:30:39.897 回答