关于Backbone.View.extend的理解
Liber 2013-06-25 17:37

用Backbone有一段时间了,一直在用

var SomeView = Backbone.View.extend({
  //...
});

这样的写法,来创建一个View类。
以前忙着先学习基本用法,也没想那么多。今天,不知为何,好奇心泛滥,终于想看看它究竟都干了些什么。

首先先说结论: 上面的代码,可以理解成这样:

var SomeView = function() {

  // Default attributes
  this.tagName = xxx;
  this.cid = xxx;
  
  // Your extend attributes
  this.model = xxx;
  this.template = xxx;
  this.render = xxx;
  this.xxx = xxx;

}

实际上,这就是一个典型的用构造模式定义的构造函数,也就是我们通俗所说的javascript的类。
这也就是我们在使用它的时候,可以用

var myView = new SomeView()

的原因。

下面我们就来进一步分析一下,为什么会是这样。
打开Backbonejs官网的源代码页面,在页面上搜索BACKBONE.VIEW,我们会看到这样一段代码:

var View = Backbone.View = function(options) {
  this.cid = _.uniqueId('view');
  this._configure(options || {});
  this._ensureElement();
  this.initialize.apply(this, arguments);
  this.delegateEvents();
};

这里就证实了上面说Backbone.View其实就是一个构造函数的说法。
接下来,我们在页面上搜索HELPERS,找到extend方法,我们会看到这样一段代码:

var extend = function(protoProps, staticProps) {

  var parent = this; // 1
  var child;
  
  if (protoProps && _.has(protoProps, 'constructor')) { // 2
    child = protoProps.constructor;
  } else {
    child = function(){ return parent.apply(this, arguments); }; // 3
  }
  
  _.extend(child, parent, staticProps);
  
  var Surrogate = function(){ this.constructor = child; };
  Surrogate.prototype = parent.prototype;
  child.prototype = new Surrogate;
  
  if (protoProps) _.extend(child.prototype, protoProps);
  
  child.__super__ = parent.prototype;

  return child;
  
};
    

先看看该方法下面的一句话:

Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;

所以,extend作为一个方法,实际上Model、View、Collection对其都进行了引用。
而当我们在使用

var SomeView = Backbone.View.extend({
  //...
});

的时候,实际上是调用了Backbone.View下面的extend方法。
然后,我们进入到这个extend方法里面看看。
1这个地方,将this赋值给一个parent变量,我们知道,在Javascript中,this关键字永远都指向函数(方法)的所有者。
所以,当我们执行Backbone.View.extend的时候,extend里面的this就指向View这个构造函数。
接着往下看,2这个地方,由于我们一般不会传constructor这个属性,所以,执行3的语句。
3语句是这里的关键。我们把这里的语句换一种方式来写:

child = new Function("return parent.apply(this, arguments);");

这样就能好理解一些了吧,这也就是我们所说的“函数是对象,函数名是指针”的原因了。详细可以去看《javascript高级程序设计》引用类型那一章的Function类型一节的内容。
执行这句的时候,child会自动产生一个prototype的属性,prototype中会有一个constructor的构造函数,这个构造函数就会指向child,而child就是这个匿名函数。
下面的语句干了一些其它的事情,需要注意的是,extend方法最后,返回了child

到这里,完成了Backbone类定义的过程。

调用的过程:
执行new的时候,会去调用返回值child的构造函数,也就是刚才提到的那个匿名函数,匿名函数会调用parent方法,parent也就是前面提到的View,而View正好又是一个构造模型的构造函数,这就是我们都熟悉的东西了,所以就有了前面的结论,从而创建了Backbone对象。

总结:
讲了这么一大堆,主要目的还是希望能起到抱砖引玉的作用,把大家探索本质的好奇心激发起来,这样才能真正的有所得,以至于最终有所为。