backbone.js源码解析:extend、Backbone.View
backbone版本:0.9.2
1.解析Backbone.Model(Collection | Router | View).extend
(1).找到extend的定义
//定义extend函数
var extend = function (protoProps, classProps) {
/*
通常我们以Backbone.XXX.extend的方式建立一个Model、View等对象类型,所以在此处this表示Backbone.Model、Backbone.View等对应构造函数(注:后面的类似概述统一以Backbone.View为具体例子)
*/
var child = inherits(this, protoProps, classProps);
child.extend = this.extend;
return child;
};
//将Model、Collection、Router、View的extend方法设置为extend
Model.extend = Collection.extend = Router.extend = View.extend = extend;
var extend = function (protoProps, classProps) {
/*
通常我们以Backbone.XXX.extend的方式建立一个Model、View等对象类型,所以在此处this表示Backbone.Model、Backbone.View等对应构造函数(注:后面的类似概述统一以Backbone.View为具体例子)
*/
var child = inherits(this, protoProps, classProps);
child.extend = this.extend;
return child;
};
//将Model、Collection、Router、View的extend方法设置为extend
Model.extend = Collection.extend = Router.extend = View.extend = extend;
(2)分析inherits函数源码
var ctor = function(){};
var inherits = function(parent, protoProps, staticProps) {
var child;
//判断protoProps是否一个原型对象(prototype),如果是则将child赋值为原型对象所属的构造函数
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
//否则将新建一个构造函数赋值给child
child = function(){
//inherits函数返回的是一个构造函数,我们会用new child()来调用此构造函数(例如:AppView = Backbone.View.extend({});var appView=new AppView();),所以此处的this指向我们new的实例(例如var appView=new AppView(),则this指向appView)
//new AppView进行的操作其实是Backbone.Model.apply(this,arguments) ,也就是说我们实例化appView的时候其实是调用Backbone.Model
parent.apply(this, arguments);
};
}
//此处parent既 1 中的 this,也就是Backbone.View,_extend是underscore.js里的一个函数,作用是将第二个及第二个以后的所有参数的所有属性和属性值设置到第一个参数上(_extend的具体实现在此不赘述,可看underscore.js的源码)
_.extend(child, parent);
//ctor是一个内容为空的构造函数,此处将其原型对象设置为Backbone.View.prototype
ctor.prototype = parent.prototype;
//将child的原型对象设置为一个ctor的实例,child.prototype.contructor指向ctor
child.prototype = new ctor();
//将Backbone.View.extend的第二个参数(一般是一个对象)的的所有属性复制到child.prototype
if (protoProps) _.extend(child.prototype, protoProps);
//将Backbone.View.extend的第三个参数(一般是一个对象)的的所有属性复制到child,也就是给child设置静态属性或方法
if (staticProps) _.extend(child, staticProps);
//执行完child.prototype=new ctor后,child.prototype.constructor已经不指向child,所以此处需要显示设置
child.prototype.constructor = child;
//EcmaScript中并没有定义__super__这个属性,此处应该是backbone记录child对应的super类
child.__super__ = parent.prototype;
return child;
};
var inherits = function(parent, protoProps, staticProps) {
var child;
//判断protoProps是否一个原型对象(prototype),如果是则将child赋值为原型对象所属的构造函数
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
//否则将新建一个构造函数赋值给child
child = function(){
//inherits函数返回的是一个构造函数,我们会用new child()来调用此构造函数(例如:AppView = Backbone.View.extend({});var appView=new AppView();),所以此处的this指向我们new的实例(例如var appView=new AppView(),则this指向appView)
//new AppView进行的操作其实是Backbone.Model.apply(this,arguments) ,也就是说我们实例化appView的时候其实是调用Backbone.Model
parent.apply(this, arguments);
};
}
//此处parent既 1 中的 this,也就是Backbone.View,_extend是underscore.js里的一个函数,作用是将第二个及第二个以后的所有参数的所有属性和属性值设置到第一个参数上(_extend的具体实现在此不赘述,可看underscore.js的源码)
_.extend(child, parent);
//ctor是一个内容为空的构造函数,此处将其原型对象设置为Backbone.View.prototype
ctor.prototype = parent.prototype;
//将child的原型对象设置为一个ctor的实例,child.prototype.contructor指向ctor
child.prototype = new ctor();
//将Backbone.View.extend的第二个参数(一般是一个对象)的的所有属性复制到child.prototype
if (protoProps) _.extend(child.prototype, protoProps);
//将Backbone.View.extend的第三个参数(一般是一个对象)的的所有属性复制到child,也就是给child设置静态属性或方法
if (staticProps) _.extend(child, staticProps);
//执行完child.prototype=new ctor后,child.prototype.constructor已经不指向child,所以此处需要显示设置
child.prototype.constructor = child;
//EcmaScript中并没有定义__super__这个属性,此处应该是backbone记录child对应的super类
child.__super__ = parent.prototype;
return child;
};
(3)总结出原型对象链
new 自定义View等() 所属类--> child(用户创造的构造函数) 原型对象--> ctor的一个实例(我们自定义的一些函数和方法都设置到此实例上) 原型对象--> Backbon.View.prototype
2.解析Backbone.View
例子:
AppView = Backbone.View.extend({});
var appView=new AppView({});
var appView=new AppView({});
(1)先看Backbone.View的源码:
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
var View = Backbone.View = function(options) {
//_.uniqueId也是underscore.js里的一个函数,作用是其内部通过闭包保存了一个整型变量,每次调用此函数整型变量都会自增1,然后用传进来的前缀(此处前缀是view)连接整型变量为一个字符串,返回该字符串
this.cid = _.uniqueId('view');
//调用Backbone.View.prototype._configure,作用是合并options与AppView.prototype.options,并且将指定属性名且值不等同于false的属性设置到this(_configure在后面有具体说明)
this._configure(options || {});
//根据this.el是否被赋值,做一些处理
this._ensureElement();
//将Backbone.View.prototype.initialize作为this的方法调用,Backbone.View.prototype.initialize默认是一个内容为空的函数。一般情况下,我们会在options中定义一个initialize函数(在AppView=Backbone.View.extend({initialize:...})这里定义,其实就是AppView.prototype.initialize)来覆盖Backbone.View.prototype.initialize
this.initialize.apply(this, arguments);
this.delegateEvents();
};
var View = Backbone.View = function(options) {
//_.uniqueId也是underscore.js里的一个函数,作用是其内部通过闭包保存了一个整型变量,每次调用此函数整型变量都会自增1,然后用传进来的前缀(此处前缀是view)连接整型变量为一个字符串,返回该字符串
this.cid = _.uniqueId('view');
//调用Backbone.View.prototype._configure,作用是合并options与AppView.prototype.options,并且将指定属性名且值不等同于false的属性设置到this(_configure在后面有具体说明)
this._configure(options || {});
//根据this.el是否被赋值,做一些处理
this._ensureElement();
//将Backbone.View.prototype.initialize作为this的方法调用,Backbone.View.prototype.initialize默认是一个内容为空的函数。一般情况下,我们会在options中定义一个initialize函数(在AppView=Backbone.View.extend({initialize:...})这里定义,其实就是AppView.prototype.initialize)来覆盖Backbone.View.prototype.initialize
this.initialize.apply(this, arguments);
this.delegateEvents();
};
(2)_configure函数被我简化如下表示:
_.extend(View.prototype, {
_configure: function(options) {
//此处this指向我们自定义View的一个实例,也就是appView
//判断this有没有options属性,如果有则将this.options与options合并然后赋值给options,通过解析extend方法得出的原型对象链我们分析得知由于此时this还没有被实例化完成且Backbon.View.prototype没有this属性,那么this.options只能来自AppView.prototype(ctor的一个实例),也就是说此处的this.options是在在Backbone.View.extend({options:...})中指定的,而options是AppView这个构造函数的第一个参数
if (this.options) options = _.extend({}, this.options, options);
//遍历viewOptions,当options[viewOptions[i]]有值时,将其赋值给this
for (var i = 0, l = viewOptions.length; i < l; i++) {
var attr = viewOptions[i];
if (options[attr]) this[attr] = options[attr];
}
this.options = options;
}
});
_configure: function(options) {
//此处this指向我们自定义View的一个实例,也就是appView
//判断this有没有options属性,如果有则将this.options与options合并然后赋值给options,通过解析extend方法得出的原型对象链我们分析得知由于此时this还没有被实例化完成且Backbon.View.prototype没有this属性,那么this.options只能来自AppView.prototype(ctor的一个实例),也就是说此处的this.options是在在Backbone.View.extend({options:...})中指定的,而options是AppView这个构造函数的第一个参数
if (this.options) options = _.extend({}, this.options, options);
//遍历viewOptions,当options[viewOptions[i]]有值时,将其赋值给this
for (var i = 0, l = viewOptions.length; i < l; i++) {
var attr = viewOptions[i];
if (options[attr]) this[attr] = options[attr];
}
this.options = options;
}
});
(3)delegateEvents代码解析
//一个正则,匹配未被空格、换行等分隔符分隔的字符串,或者以空格、换行等分隔符分隔为2段的字符串
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
delegateEvents: function(events) {
//判断events属性(可以使一个对象,也可以是一个返回对象的函数)是否存在,如果不存在则返回
if (!(events || (events = getValue(this, 'events')))) return;
//移除this.$el上绑定的事件
this.undelegateEvents();
//遍历所有指定的事件
for (var key in events) {
//events[key]要么是一个函数,要么是View的一个方法名
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
//如果未找到事件对于的函数,则抛出移除
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
//对事件名进行切割,例如"click .toggle"会被切割为match=['click .toggle','click','.toggle']
var match = key.match(delegateEventSplitter);
//设置事件名,以及选择器
var eventName = match[1], selector = match[2];
//_.bind是underscore.js的方法,此处调用此方法,得到一个函数method,函数内部的this引用的是我们自定义View的一个实例,也就是appView
method = _.bind(method, this);
//为事件添加命名空间,以便可以通过命名空间一次性移除所有不同类型的事件
eventName += '.delegateEvents' + this.cid;
//下面使用jQuery进行事件绑定
//如果选择器为空字符串,则给$el绑定事件
if (selector === '') {
this.$el.bind(eventName, method);
} else {
//根据选择器,为this.$el的子级绑定事件
this.$el.delegate(selector, eventName, method);
}
}
}
//附上undelegateEvents的源码
undelegateEvents: function() {
//移除当前通过this.$el.bind或者this.$el.delegate绑定的且命名空间为 '.delegateEvents' + this.cid 的所有事件(通过命名空间来移除)
this.$el.unbind('.delegateEvents' + this.cid);
}
//附上getValue的源码
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
delegateEvents: function(events) {
//判断events属性(可以使一个对象,也可以是一个返回对象的函数)是否存在,如果不存在则返回
if (!(events || (events = getValue(this, 'events')))) return;
//移除this.$el上绑定的事件
this.undelegateEvents();
//遍历所有指定的事件
for (var key in events) {
//events[key]要么是一个函数,要么是View的一个方法名
var method = events[key];
if (!_.isFunction(method)) method = this[events[key]];
//如果未找到事件对于的函数,则抛出移除
if (!method) throw new Error('Method "' + events[key] + '" does not exist');
//对事件名进行切割,例如"click .toggle"会被切割为match=['click .toggle','click','.toggle']
var match = key.match(delegateEventSplitter);
//设置事件名,以及选择器
var eventName = match[1], selector = match[2];
//_.bind是underscore.js的方法,此处调用此方法,得到一个函数method,函数内部的this引用的是我们自定义View的一个实例,也就是appView
method = _.bind(method, this);
//为事件添加命名空间,以便可以通过命名空间一次性移除所有不同类型的事件
eventName += '.delegateEvents' + this.cid;
//下面使用jQuery进行事件绑定
//如果选择器为空字符串,则给$el绑定事件
if (selector === '') {
this.$el.bind(eventName, method);
} else {
//根据选择器,为this.$el的子级绑定事件
this.$el.delegate(selector, eventName, method);
}
}
}
//附上undelegateEvents的源码
undelegateEvents: function() {
//移除当前通过this.$el.bind或者this.$el.delegate绑定的且命名空间为 '.delegateEvents' + this.cid 的所有事件(通过命名空间来移除)
this.$el.unbind('.delegateEvents' + this.cid);
}
//附上getValue的源码
var getValue = function(object, prop) {
if (!(object && object[prop])) return null;
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
};
(4) _.bind源码解析,将其代码简化如下:
FuncProto = Function.prototype;
nativeBind = FuncProto.bind;
var ArrayProto = Array.prototype;
var slice = ArrayProto.slice;
var ctor = function(){};
_.bind = function bind(func, context) {
var bound, args;
//判断当前浏览器是否实现了EcmaScript 5.1的Function.prototype.bind方法,如果是,则直接调用此方法并返回一个函数
//如果在此处返回,那么返回的函数参数列表是:(arguments[2],arguments[3],...),且函数里面的this引用的是context
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
//如果func不是一个函数,则返回
if (!_.isFunction(func)) throw new TypeError;
//获取第三个及其后面的所有参数(也就是获取context以后的所有参数)
args = slice.call(arguments, 2);
return bound = function() {
//如果不是用bound当作构造函数,则在此处返回一个函数
if (!(this instanceof bound))
{
//如果在此处返回,那么返回的函数参数列表是:(args[0],args[1],...,arguments[0],arguments[1],...),且函数里面的this引用的是context
//注意,args是调用外层_.bind函数时的arguments的一段(利用闭包实现),arguments则是调用此处返回的函数时引用的参数对象
return func.apply(context, args.concat(slice.call(arguments)));
}
//ctor是一个内容为空的构造函数,此处将其原型对象设置为func.prototype
ctor.prototype = func.prototype;
//实例化一个ctor对象
var self = new ctor;
//上面两句代码执行以后,构造了一个作用域链:self 所属类--> ctor 原型对象--> func.prototype
//调用func函数,得到一个返回对象。返回的函数参数列表是:(args[0],args[1],...,arguments[0],arguments[1],...),且函数里面的this引用的是self
//注意,args是调用外层_.bind函数时的arguments的一段(利用闭包实现),arguments则是调用此处返回的函数时引用的参数对象
var result = func.apply(self, args.concat(slice.call(arguments)));
//下面代码是返回一个对象,注意:在构造函数里返回对象,则构造函数中this引用的对象会被抛弃
//如果result是引用类型(如Object,Array等),则返回result
if (Object(result) === result)
{
return result;
}
//否则返回self
return self;
};
};
nativeBind = FuncProto.bind;
var ArrayProto = Array.prototype;
var slice = ArrayProto.slice;
var ctor = function(){};
_.bind = function bind(func, context) {
var bound, args;
//判断当前浏览器是否实现了EcmaScript 5.1的Function.prototype.bind方法,如果是,则直接调用此方法并返回一个函数
//如果在此处返回,那么返回的函数参数列表是:(arguments[2],arguments[3],...),且函数里面的this引用的是context
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
//如果func不是一个函数,则返回
if (!_.isFunction(func)) throw new TypeError;
//获取第三个及其后面的所有参数(也就是获取context以后的所有参数)
args = slice.call(arguments, 2);
return bound = function() {
//如果不是用bound当作构造函数,则在此处返回一个函数
if (!(this instanceof bound))
{
//如果在此处返回,那么返回的函数参数列表是:(args[0],args[1],...,arguments[0],arguments[1],...),且函数里面的this引用的是context
//注意,args是调用外层_.bind函数时的arguments的一段(利用闭包实现),arguments则是调用此处返回的函数时引用的参数对象
return func.apply(context, args.concat(slice.call(arguments)));
}
//ctor是一个内容为空的构造函数,此处将其原型对象设置为func.prototype
ctor.prototype = func.prototype;
//实例化一个ctor对象
var self = new ctor;
//上面两句代码执行以后,构造了一个作用域链:self 所属类--> ctor 原型对象--> func.prototype
//调用func函数,得到一个返回对象。返回的函数参数列表是:(args[0],args[1],...,arguments[0],arguments[1],...),且函数里面的this引用的是self
//注意,args是调用外层_.bind函数时的arguments的一段(利用闭包实现),arguments则是调用此处返回的函数时引用的参数对象
var result = func.apply(self, args.concat(slice.call(arguments)));
//下面代码是返回一个对象,注意:在构造函数里返回对象,则构造函数中this引用的对象会被抛弃
//如果result是引用类型(如Object,Array等),则返回result
if (Object(result) === result)
{
return result;
}
//否则返回self
return self;
};
};
(5) 关于EcmaScript 5.1的Function.prototype.bind方法
Function.prototype.bind(thisArg [, arg1 [, arg2, …]])
Function.prototype.bind返回一个新的函数对象,该函数对象的this绑定到了thisArg参数上。从本质上讲,这允许你在其他对象链中执行一个函数。
例子如下:
function locate(){
console.log(this.location);
}
function Maru(location){
this.location = location;
}
var kitty = new Maru("cardboard box");
var locateMaru = locate.bind(kitty);
locateMaru();
console.log(this.location);
}
function Maru(location){
this.location = location;
}
var kitty = new Maru("cardboard box");
var locateMaru = locate.bind(kitty);
locateMaru();