为什么“这个”与这个Backbone View不一致?

时间:2013-10-16 11:48:24

标签: javascript backbone.js

考虑代码(also on 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 fiddle完成),然后我的控制台显示'this' is bound to the outerview instance: false。似乎this已绑定到InnerView实例。

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

2 个答案:

答案 0 :(得分:2)

这是正确的行为。在第一个例子中,这指的是“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)
);

答案 1 :(得分: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.modelobj[implementation](name, callback, this); - &gt; this.model['on']('change:name', this.parent.onNameChange, this);

obj[implementation](name, callback, this);的第3个参数实际上被Backbone称为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;
},

以下是tirgger事件的方式

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.onNameChange,则this中的onNameChange将绑定到错误的对象。