说明
前段时间略忙,终于找到时间看看backbone代码。
正如知友们说的那样,backbone简单、随性。 代码简单的看一眼,就能知道作者的思路。因为简单,所以随性,可以很自由的和其他类库大搭配使用,不太要求特别的格式。
本文会关注backbone实现的细节,整体框架在博客园的一位朋友已经总结的很好了。链接:
本文Bakcbone版本:
// 定义Backbone版本 Backbone.VERSION = ' 0.9.2 ' ;
照例提点问题,有目的的读源码。
- 怎么和模板引擎配合使用的。
- RESTful JSON 接口从服务器检索到的数据
- 通过源码,了解到平时使用时的哪些需要注意的细节。
- 当models中值被改变时自动触发一个”change”事件、所有用于展示models数据的views都会侦听到这个事件,然后进行重新渲染。如何实现?
- 如何兼容IE6+
- view model collection怎么关联的。
整体框架
(function() { Backbone.Events // 自定义事件 Backbone.Model // 模型构造函数和原型扩展 Backbone.Collection // 集合构造函数和原型扩展 Backbone.Router // 路由配置器构造函数和原型扩展 Backbone.History // 路由器构造函数和原型扩展 Backbone.View // 视图构造函数和原型扩展 Backbone.sync // 异步请求工具方法 var extend = function (protoProps, classProps) { ... } // 自扩展函数 Backbone.Model.extend = Backbone.Collection.extend = Backbone.Router.extend = Backbone.View.extend = extend; // 自扩展方法 }).call( this );
本文的思路是按照这个框架,提炼每个部分的重要部分,解决上面提出的问题,并注释平时使用时需要注意的细节。
Events
var Events = Backbone.Events = { // 该方法类似与DOM Level2中的addEventListener方法 // events允许指定多个事件名称, 通过空白字符进行分隔(如空格, 制表符等) // 当事件名称为"all"时, 在调用trigger方法触发任何事件时, 均会调用"all"事件中绑定的所有回调函数 on : function(events, callback, context) { var calls, event, node, tail, list; if(!callback) return this; // eventSplitter = /\s+/; 把多个事件名组成的字符串分开。 events = events.split(eventSplitter); // 所有事件都存在this._callbacks这个数组里面 calls = this._callbacks || ( this._callbacks = {}); while( event = events.shift()) { list = calls[ event]; node = list ? list.tail : {}; node.next = tail = {}; node.context = context; node.callback = callback; // 重新组装当前事件的回调列表, 列表中已经加入了本次回调事件 calls[ event] = { tail : tail, next : list ? list.next : node }; } // 返回当前对象, 方便进行方法链调用 return this; }, // - 如果context为空, 则移除所有的callback指定的函数 // - 如果callback为空, 则移除事件中所有的回调函数 // - 如果events为空, 但指定了callback或context, 则移除callback或context指定的回调函数(不区分事件名称) // - 如果没有传递任何参数, 则移除对象中绑定的所有事件和回调函数 off : function(events, callback, context) { // code... return this; }, // 触发已经定义的一个或多个事件, 依次执行绑定的回调函数列表 trigger : function(events) { var event, node, calls, tail, args, all, rest; // 当前对象没有绑定任何事件 if(!( calls = this._callbacks)) return this; // 获取回调函数列表中绑定的"all"事件列表 all = calls.all; // 将需要触发的事件名称, 按照eventSplitter规则解析为一个数组 events = events.split(eventSplitter); // 将trigger从第2个之后的参数, 记录到rest变量, 将依次传递给回调函数 rest = slice.call(arguments, 1); // 循环需要触发的事件列表 while( event = events.shift()) { // 此处的node变量记录了当前事件的所有回调函数列表 if( node = calls[ event]) { tail = node.tail; // node变量的值, 按照事件的绑定顺序, 被依次赋值为绑定的单个回调事件对象 // 最后一次绑定的事件next属性, 与tail引用同一个对象, 以此作为是否到达列表末尾的判断依据 while(( node = node.next) !== tail) { // 执行所有绑定的事件, 并将调用trigger时的参数传递给回调函数 node.callback.apply(node.context || this, rest); } } if( node = all) { tail = node.tail; // 与调用普通事件的回调函数不同之处在于, all事件会将当前调用的事件名作为第一个参数传递给回调函数 args = [ event].concat(rest); // 遍历并执行"all"事件中的回调函数列表 while(( node = node.next) !== tail) { node.callback.apply(node.context || this, args); } } } return this; } }; // 绑定事件与释放事件的别名, 也为了同时兼容Backbone以前的版本 Events.bind = Events.on; Events.unbind = Events.off;
Model
Model 比较常用,很多细节,所以列出了几个重要的函数,注释了一些重要细节。
_.extend(Model.prototype, Events, { // code.. // 设置模型中的数据, 如果key值不存在, 则作为新的属性添加到模型, 如果key值已经存在, 则修改为新的值 set : function(key, value, options) { var attrs, attr, val; // 参数形式允许key-value对象形式, 或通过key, value两个参数进行单独设置 // 如果key是一个对象, 则认定为使用对象形式设置, 第二个参数将被视为options参数 if(_.isObject(key) || key == null) { attrs = key; options = value; } else { attrs = {}; attrs[key] = value; } // options配置项必须是一个对象, 如果没有设置options则默认值为一个空对象 options || ( options = {}); if(!attrs) return this; // 如果被设置的数据对象属于Model类的一个实例, 则将Model对象的attributes数据对象赋给attrs // 一般在复制一个Model对象的数据到另一个Model对象时, 会执行该动作 if( attrs instanceof Model) attrs = attrs.attributes; // 一般在复制一个Model对象的数据到另一个Model对象时, 但仅仅需要复制Model中的数据而不需要复制值时执行该操作 if(options.unset) for(attr in attrs) attrs[attr] = void 0; // 如果设置了validate() 函数,则需要验证 if(! this._validate(attrs, options)) return false; // 如果设置的id属性名被包含在数据集合中, 则将id覆盖到模型的id属性 // 这是为了确保在自定义id属性名后, 访问模型的id属性时, 也能正确访问到id if( this.idAttribute in attrs) this.id = attrs[ this.idAttribute]; var changes = options.changes = {}; // now记录当前模型中的数据对象 var now = this.attributes; // escaped记录当前模型中通过escape缓存过的数据 var escaped = this._escapedAttributes; // prev记录模型中数据被改变之前的值 var prev = this._previousAttributes || {}; // code.. // 如果没有配置silent参数,则需要触发change函数。 if(!options.silent) this.change(options); return this; }, // 从服务器获取默认的模型数据, 获取数据后使用set方法将数据填充到模型, 因此如果获取到的数据与当前模型中的数据不一致, 将会触发change事件 fetch : function(options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; // 当获取数据成功后填充数据并调用自定义成功回调函数 options.success = function(resp, status, xhr) { // 如果填充数据时验证失败, 则不会调用自定义success回调函数 if(!model. set(model.parse(resp, xhr), options)) return false; // 调用自定义的success回调函数 if(success) success(model, resp); }; // 请求发生错误时通过wrapError处理error事件 options.error = Backbone.wrapError(options.error, model, options); // 所有的读取数据(Model, Collection)都是通过sync提供的HTTP方法操作 return ( this.sync || Backbone.sync).call( this, ' read ', this, options); }, // 保存模型中的数据到服务器 save : function(key, value, options) { // attrs存储需要保存到服务器的数据对象 var attrs, current; // 支持设置单个属性的方式 key: value // 支持对象形式的批量设置方式 {key: value} if(_.isObject(key) || key == null) { // 如果key是一个对象, 则认为是通过对象方式设置 // 此时第二个参数被认为是options attrs = key; options = value; } else { // 如果是通过key: value形式设置单个属性, 则直接设置attrs attrs = {}; attrs[key] = value; } // 配置对象必须是一个新的对象 options = options ? _.clone(options) : {}; // 如果在options中设置了wait选项, 则被改变的数据将会被提前验证, 且服务器没有响应新数据(或响应失败)时, 本地数据会被还原为修改前的状态 // 如果没有设置wait选项, 则无论服务器是否设置成功, 本地数据均会被修改为最新状态 if(options.wait) { // 对需要保存的数据提前进行验证 if(! this._validate(attrs, options)) return false; current = _.clone( this.attributes); } var model = this; // 在options中可以指定保存数据成功后的自定义回调函数 var success = options.success; options.success = function(resp, status, xhr) { var serverAttrs = model.parse(resp, xhr); if(options.wait) { delete options.wait; serverAttrs = _.extend(attrs || {}, serverAttrs); } // 如果调用set方法时验证失败, 则不会调用自定义的success回调函数 if(!model. set(serverAttrs, options)) return false; if(success) { // 调用响应成功后自定义的success回调函数 success(model, resp); } else { // 如果没有指定自定义回调, 则默认触发sync事件 model.trigger( ' sync ', model, resp, options); } }; // 请求发生错误时通过wrapError处理error事件 options.error = Backbone.wrapError(options.error, model, options); var method = this.isNew() ? ' create ' : ' update '; var xhr = ( this.sync || Backbone.sync).call( this, method, this, options); // 如果设置了options.wait, 则将数据还原为修改前的状态 // 此时保存的请求还没有得到响应, 因此如果响应失败, 模型中将保持修改前的状态, 如果服务器响应成功, 则会在success中设置模型中的数据为最新状态 if(options.wait) this. set(current, silentOptions); return xhr; }, // code.. });
Collection
var Collection = Backbone.Collection = function(models, options) { options || ( options = {}); if(options.model) this.model = options.model; // 如果设置了comparator属性, 则集合中的数据将按照comparator方法中的排序算法进行排序(在add方法中会自动调用) // 当然也可以服务器做好了传回来,但是如果前后属于不同团队就不好做了。 if(options.comparator) this.comparator = options.comparator; // 实例化时重置集合的内部状态(第一次调用时可理解为定义状态) this._reset(); this.initialize.apply( this, arguments); // 首次调用时设置了silent参数, 因此不会触发"reset"事件 if(models) this.reset(models, { silent : true, parse : options.parse }); }; _.extend(Collection.prototype, Events, { // 定义集合的模型类, 模型类必须是一个Backbone.Model的子类 model : Model, initialize : function() { }, // 返回一个数组, 包含了集合中每个模型的数据对象 toJSON : function(options) { return this.map(function(model) { return model.toJSON(options); }); }, // 默认会触发"add"事件, 如果在options中设置了silent属性, 可以关闭此次事件触发 // 传入的models可以是一个或一系列的模型对象(Model类的实例), 如果在集合中设置了model属性, 则允许直接传入数据对象(如 {name: 'test'}), 将自动将数据对象实例化为model指向的模型对象 add : function(models, options) { var i, index, length, model, cid, id, cids = {}, ids = {}, dups = []; options || ( options = {}); // models必须是一个数组, 如果只传入了一个模型, 则将其转换为数组 models = _.isArray(models) ? models.slice() : [models]; // 遍历需要添加的模型列表, 遍历过程中, 将执行以下操作: // - 将数据对象转化模型对象 // - 建立模型与集合之间的引用 // - 记录无效和重复的模型, 并在后面进行过滤 for( i = 0, length = models.length; i < length; i++) { // 当前模型的cid和id cid = model.cid; id = model.id; // dups数组中记录了无效或重复的模型索引(models数组中的索引), 并在下一步进行过滤删除 if(cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) { dups.push(i); continue; } cids[cid] = ids[id] = model; } // 从models中删除无效或重复的模型, 保留目前集合中真正需要添加的模型列表 i = dups.length; while(i--) { models.splice(dups[i], 1); } // code ... // 遍历新增加的模型列表 for( i = 0, length = this.models.length; i < length; i++) { if(!cids[( model = this.models[i]).cid]) continue; options.index = i; // 触发模型的"add"事件, 因为集合监听了模型的"all"事件, 因此在_onModelEvent方法中, 集合也将触发"add"事件 // 详细信息可参考Collection.prototype._onModelEvent方法 model.trigger( ' add ', model, this, options); } return this; }, // 如果没有设置options.silent参数, 将触发模型的remove事件, 同时将触发集合的remove事件(集合通过_onModelEvent方法监听了模型的所有事件) remove : function(models, options) { var i, l, index, model; options || ( options = {}); models = _.isArray(models) ? models.slice() : [models]; // 遍历需要移除的模型列表 for( i = 0, l = models.length; i < l; i++) { model = this.getByCid(models[i]) || this. get(models[i]); if(!model) continue; delete this._byId[model.id]; delete this._byCid[model.cid]; index = this.indexOf(model); this.models.splice(index, 1); this.length--; // 如果没有设置silent属性, 则触发模型的remove事件 if(!options.silent) { options.index = index; model.trigger( ' remove ', model, this, options); } this._removeReference(model); } return this; }, push : function(model, options) { model = this._prepareModel(model, options); this.add(model, options); return model; }, // code .. });
Router & History
var Router = Backbone.Router = function(options) { options || ( options = {}); if(options.routes) this.routes = options.routes; this._bindRoutes(); this.initialize.apply( this, arguments); }; _.extend(Router.prototype, Events, { // 将一个路由规则绑定给一个监听事件, 当URL片段匹配该规则时, 会自动调用触发该事件 route : function(route, name, callback) { // 创建history实例, Backbone.history是一个单例对象, 只在第一次创建路由器对象时被实例化 Backbone.history || (Backbone.history = new History); // code ... Backbone.history.route(route, _.bind(function(fragment) { var args = this._extractParameters(route, fragment); // 调用callback路由监听事件, 并将参数传递给监听事件 callback && callback.apply( this, args); this.trigger.apply( this, [ ' route: ' + name].concat(args)); // 触发history实例中绑定的route事件, 当路由器匹配到任何规则时, 均会触发该事件 Backbone.history.trigger( ' route ', this, name, args); }, this)); return this; }, // code .. }); // History一般不会被直接调用, 在第一次实例化Router对象时, 将自动创建一个History的单例(通过Backbone.history访问) var History = Backbone.History = function() { this.handlers = []; // checkUrl方法用于在监听到URL发生变化时检查并调用loadUrl方法 _.bindAll( this, ' checkUrl '); }; _.extend(History.prototype, Events, { // 当用户使用低版本的IE浏览器(不支持onhashchange事件)时, 通过心跳监听路由状态的变化 // interval属性设置心跳频率(毫秒), 该频率如果太低可能会导致延迟, 如果太高可能会消耗CPU资源(需要考虑用户使用低端浏览器时的设备配置) interval : 50, // 获取location中Hash字符串(锚点#后的片段) getHash : function(windowOverride) { // 如果传入了一个window对象, 则从该对象中获取, 否则默认从当前window对象中获取 var loc = windowOverride ? windowOverride.location : window.location; // 将锚点(#)后的字符串提取出来并返回 var match = loc.href.match(/#(.*)$/); return match ? match[ 1] : ''; }, // 根据当前设置的路由方式, 处理并返回当前URL中的路由片段 getFragment : function(fragment, forcePushState) { // fragment是通过getHash或从URL中已经提取的待处理路由片段(如 #/id/1288) if(fragment == null) { if( this._hasPushState || forcePushState) { // 使用了pushState方式进行路由 fragment = window.location.pathname; // search记录当前页面后的参数内容 var search = window.location.search; // 将路径和参数合并在一起, 作为待处理的路由片段 if(search) fragment += search; } else { // 使用了hash方式进行路由 // 通过getHash方法获取当前锚点(#)后的字符串作为路由片段 fragment = this.getHash(); } } if(!fragment.indexOf( this.options.root)) fragment = fragment.substr( this.options.root.length); // 如果URL片段首字母为"#"或"/", 则去除该字符 return fragment.replace(routeStripper, ''); }, // 该方法作为整个路由的调度器, 它将针对不同浏览器监听URL片段的变化, 负责验证并通知到监听函数 start : function(options) { // (如果手动设置了options.pushState为true, 且浏览器支持pushState特性, 则会使用pushState方式) this._wantsHashChange = this.options.hashChange !== false; // _wantsPushState属性记录是否希望使用pushState方式来记录和导航路由器 // pushState是HTML5中为window.history添加的新特性, 如果没有手动声明options.pushState为true, 则默认将使用hash方式 this._wantsPushState = !! this.options.pushState; // _hasPushState属性记录浏览器是否支持pushState特性 this._hasPushState = !!( this.options.pushState && window.history && window.history.pushState); var fragment = this.getFragment(); // documentMode是IE浏览器的独有属性, 用于标识当前浏览器使用的渲染模式 var docMode = document.documentMode; var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); if(oldIE) { // 如果用户使用低版本的IE浏览器, 不支持popstate和onhashchange事件 // 向DOM中插入一个隐藏的iframe, 并通过改变和心跳监听该iframe的URL实现路由 this.iframe = $( ' <iframe src="javascript:0" tabindex="-1" /> ').hide().appendTo( ' body ')[ 0].contentWindow; this.navigate(fragment); } // 开始监听路由状态变化 if( this._hasPushState) { // 如果使用了pushState方式路由, 且浏览器支持该特性, 则将popstate事件监听到checkUrl方法 $(window).bind( ' popstate ', this.checkUrl); } else if( this._wantsHashChange && ( ' onhashchange ' in window) && !oldIE) { // 如果使用Hash方式进行路由, 且浏览器支持onhashchange事件, 则将hashchange事件监听到checkUrl方法 $(window).bind( ' hashchange ', this.checkUrl); } else if( this._wantsHashChange) { // 对于低版本的浏览器, 通过setInterval方法心跳监听checkUrl方法, interval属性标识心跳频率 this._checkUrlInterval = setInterval( this.checkUrl, this.interval); } // code .. }, // 停止history对路由的监控, 并将状态恢复为未监听状态 stop : function() { // 解除对浏览器路由的onpopstate和onhashchange事件的监听 $(window).unbind( ' popstate ', this.checkUrl).unbind( ' hashchange ', this.checkUrl); // 停止对于低版本的IE浏览器的心跳监控 clearInterval( this._checkUrlInterval); // 恢复started状态, 便于下次重新调用start方法 History.started = false; }, // 该方法在onpopstate和onhashchange事件被触发后自动调用, 或者在低版本的IE浏览器中由setInterval心跳定时调用 checkUrl : function(e) { // 获取当前的URL片段 var current = this.getFragment(); // 对低版本的IE浏览器, 将从iframe中获取最新的URL片段并赋给current变量 if(current == this.fragment && this.iframe) current = this.getFragment( this.getHash( this.iframe)); // 如果当前URL与上一次的状态没有发生任何变化, 则停止执行 if(current == this.fragment) return false; // 执行到这里, URL已经发生改变, 调用navigate方法将URL设置为当前URL if( this.iframe) this.navigate(current); // 调用loadUrl方法, 检查匹配的规则, 并执行规则绑定的方法 this.loadUrl() || this.loadUrl( this.getHash()); }, // code .. });
View
// Backbone.View 视图相关 var View = Backbone.View = function(options) { // 为每一个视图对象创建一个唯一标识, 前缀为"view" this.cid = _.uniqueId( ' view '); this._configure(options || {}); this._ensureElement(); this.initialize.apply( this, arguments); this.delegateEvents(); }; // viewOptions列表记录一些列属性名, 在构造视图对象时, 如果传递的配置项中包含这些名称, 则将属性复制到对象本身 var viewOptions = [ ' model ', ' collection ', ' el ', ' id ', ' attributes ', ' className ', ' tagName ']; _.extend(View.prototype, Events, { // 如果在创建视图对象时, 没有设置指定的el元素, 则会通过make方法创建一个元素, tagName为创建元素的默认标签 tagName : ' div ', // code .. // 移除当前视图的$el元素 remove : function() { // 通过调用jQuery或Zepto的remove方法, 因此在第三方库中会同时移除该元素绑定的所有事件和数据 this.$el.remove(); return this; }, // 该方法用于在内部创建this.el时自动调用 make : function(tagName, attributes, content) { var el = document.createElement(tagName); if(attributes) $(el).attr(attributes); if(content) $(el).html(content); return el; }, // 为视图对象设置标准的$el及el属性, 该方法在对象创建时被自动调用 setElement : function(element, delegate) { // this.$el 存放Jquery或其他库的示例对象 this.$el = ( element instanceof $) ? element : $(element); // this.el存放标准的DOM对象 this.el = this.$el[ 0]; // code ... return this; }, // 为视图元素绑定事件 // events参数配置了需要绑定事件的集合, 格式如('事件名称 元素选择表达式' : '事件方法名称/或事件函数'): // { // 'click #title': 'edit', // 'click .save': 'save' // 'click span': function() {} // } // 该方法在视图对象初始化时会被自动调用, 并将对象中的events属性作为events参数(事件集合) delegateEvents : function(events) { if(!(events || ( events = getValue( this, ' events ')))) return; // 取消当前已经绑定过的events事件 this.undelegateEvents(); for( var key in events) { // code ... // 解析事件表达式(key), 从表达式中解析出事件的名字和需要操作的元素 // 例如 'click #title'将被解析为 'click' 和 '#title' 两部分, 均存放在match数组中 var match = key.match(delegateEventSplitter); // eventName为解析后的事件名称 // selector为解析后的事件元素选择器表达式 var eventName = match[ 1], selector = match[ 2]; method = _.bind(method, this); // 设置事件名称, 在事件名称后追加标识, 用于传递给jQuery或Zepto的事件绑定方法 eventName += ' .delegateEvents ' + this.cid; if(selector === '') { this.$el.bind(eventName, method); } else { this.$el. delegate(selector, eventName, method); } } }, // 在实例化视图对象时设置初始配置 // 将传递的配置覆盖到对象的options中 // 将配置中与viewOptions列表相同的配置复制到对象本身, 作为对象的属性 _configure : function(options) { // 如果对象本身设置了默认配置, 则使用传递的配置进行合并 if( this.options) options = _.extend({}, this.options, options); // 遍历viewOptions列表 for( var i = 0, l = viewOptions.length; i < l; i++) { // attr依次为viewOptions中的属性名 var attr = viewOptions[i]; // 将options配置中与viewOptions相同的配置复制到对象本身, 作为对象的属性 if(options[attr]) this[attr] = options[attr]; } this.options = options; }, _ensureElement : function() { if(! this.el) { // 如果没有设置el属性, 则创建默认元素 var attrs = getValue( this, ' attributes ') || {}; if( this.id) attrs.id = this.id; if( this.className) attrs[ ' class '] = this.className; // 通过make方法创建元素, 并调用setElement方法将元素设置为视图所使用的标准元素 this.setElement( this.make( this.tagName, attrs), false); } else { // 如果设置了el属性, 则直接调用setElement方法将el元素设置为视图的标准元素 this.setElement( this.el, false); } } });
Backbone.sync
var methodMap = { ' create ' : ' POST ', ' update ' : ' PUT ', ' delete ' : ' DELETE ', ' read ' : ' GET ' }; // Async用于在Backbone中操作数据时, 向服务器发送请求同步数据状态, 以建立与服务器之间的连接 // sync发送默认通过第三方库(jQuery, Zepto等) $.ajax方法发送请求, 因此如果要调用状态同步相关的方法, 需要第三方库支持 // Model Collection save 或者fetch都用这个这个类。 Backbone.sync = function(method, model, options) { // 根据CRUD方法名定义与服务器交互的方法(POST, GET, PUT, DELETE) var type = methodMap[method]; // params将作为请求参数对象传递给第三方库的$.ajax方法 var params = { // 请求类型 type : type, // 数据格式默认为json dataType : ' json ' }; // 如果在发送请求时没有在options中设置url地址, 将会通过模型对象的url属性或方法来获取url if(!options.url) { params.url = getValue(model, ' url ') || urlError(); } if(!options.data && model && (method == ' create ' || method == ' update ')) { params.contentType = ' application/json '; params.data = JSON.stringify(model.toJSON()); } if(Backbone.emulateHTTP) { // 如果操作类型为PUT或DELETE if(type === ' PUT ' || type === ' DELETE ') { // 将操作名称存放到_method参数发送到服务器 if(Backbone.emulateJSON) params.data._method = type; // 实际以POST方式进行提交, 并发送X-HTTP-Method-Override头信息 params.type = ' POST '; params.beforeSend = function(xhr) { xhr.setRequestHeader( ' X-HTTP-Method-Override ', type); }; } } // 通过第三方库的$.ajax方法向服务器发送请求同步数据状态 return $.ajax(_.extend( params, options)); };
使用
var extend = function(protoProps, classProps) { // child存储已经实现继承自当前类的子类(Function) // protoProps设置子类原型链中的属性 // classProps设置子类的静态属性 var child = inherits( this, protoProps, classProps); // 将extend函数添加到子类, 因此调用子类的extend方法便可实现对子类的继承 child.extend = this.extend; // 返回实现继承的子类 return child; }; // 为Model, Collection, Router和View类实现继承机制 每次使用只需要 Backbone.View.extend({...}); Model.extend = Collection.extend = Router.extend = View.extend = extend;
说明
本次分析基本上对翻译源码注释,中间省略了一些个人认为对理解代码实现和平时应用关系不大的代码。