首页 > 代码库 > Backbone笔记(续)
Backbone笔记(续)
Backbone
Bockbone 总览
Backbone 与 MVC
模式:解决某一类问题的通用方案 - 套路
MVC:一种架构模式,解耦代码,分离关注点
M(Model) - 数据模型 V(View) - 表现视图 C(Controller) - 控制器
Backbone 与 SPA
传统web应用与 SPA 的区别:
状态概念代替了页面概念
http://www.example.com/page1 http://www.example.com/page2 http://www.example.com/#/state1 http://www.example.com/#/state2
Backbone 核心
(1)Backbone 命名空间下提供五个类:
- Backbone.Model
- Backbone.Collection
- Backbone.View
- Backbone.Router
- Backbone.History
每个类都可以实例化:
var model1 = new Backbone.Model({name: ‘Alice‘}); var model2 = new Backbone.Model({name: ‘Brooks‘}); var collecion = new Collection(); collection.add(model1); collection.add(model2); /* 注意: Backbone.History 负责整个页面的历史记录和导航行为管理,因此在整个生命周期内应该保持唯一,Backbone.history 引用这个唯一的 Backbone.History 实例: */ Backbone.history.start();
(2)使用 extend( protoProp, staticProp )
方法扩展子类:
// extend() 方法第一个参数是需要扩展的实例方法,第二个参数是需要扩展的静态方法 var Man = Backbone.Model.extend({ name: ‘Jack‘, intro: function() { alert(‘My name is ‘ + this.name + ‘.‘); } }, { race: ‘Human‘ }); var man = new Man(); console.log(man.name); // Jack man.intro(); // My name is Jack. console.log(Man.race); // Human
通过 extend() 方法扩展出来的子类,原型继承于父类,但是在实例化时不会调用父类的构造方法:
Man.prototype instanceof Backbone.Model; // true Man.__super__ === Backbone.Model.prototype; // true
(3)数据持久化支持:
Backbone 数据的持久化支持由全局方法 Backbone.sync() 提供,该方法默认使用 $.ajax() 方法发送 RESTful 请求的方式同步数据到后端服务器:
REST风格的请求:
create → POST /collection
read → GET /collection[/id]
update → PUT /collection/id
patch → PATCH /collection/id
delete → DELETE /collection/id
用户也可以通过重写 Backbone.sync() 方法采用不同的持久化方案,如 LocalStorage:
/* Backbone.sync(method, model, [options]): method – CRUD 方法 ("create", "read", "update", or "delete") model – 要被保存的模型(或要被读取的集合) options – 成功和失败的回调函数,以及所有 jQuery 请求支持的选项 */ Backbone.sync = function(method, model, options){ switch(method){ case ‘create‘: localStorage.setItem(model.cid,JSON.stringify(model));break; case ‘update‘: localStorage.setItem(model.cid,JSON.stringify(model));break; case ‘read‘: model = JSON.parse(localStorage.getItem(model.cid));break; case ‘delete‘: localStorage.removeItem(model.cid); model = {};break; default: break; } console.log(method); } var model = new Backbone.Model({name:‘旺财‘}); model.save(); // -> create // model.fetch(); -> read
(4)事件支持
Backbone.Events 是一个对象,拥有 on()/off()/trigger()
方法绑定、解绑、触发这个对象上的事件,也拥有 listenTo()/stopListening()
方法监听、解除监听别的对象上发生的事件。
Backbone 下的所有类的原型都通过拷贝继承的方式继承了这些方法,因此每个类的实例都能够使用这些方法获得事件的支持:
var model = new Backbone.Model({ age: 18 }); model.on(‘xxx‘, function(){ console.log( ‘xxx is triggered!‘ ); }); model.trigger(‘xxx‘);
每个类内置的事件列表如下:
"add" (model, collection, options):当一个model(模型)被添加到一个collection(集合)时触发。
"remove" (model, collection, options):当一个model(模型)从一个collection(集合)中被删除时触发。
"reset" (collection, options):当该collection(集合)的全部内容已被替换时触发。 "sort" (collection, options):当该collection(集合)已被重新排序时触发。 "change" (model, options):当一个model(模型)的属性改变时触发。 "change:[attribute]" (model, value, options):当一个model(模型)的某个特定属性被更新时触发。 "destroy" (model, collection, options):当一个model(模型)被destroyed(销毁)时触发。 "request" (model_or_collection, xhr, options):当一个model(模型)或collection(集合)开始发送请求到服务器时触发。 "sync" (model_or_collection, resp, options):当一个model(模型)或collection(集合)成功同步到服务器时触发。 "error" (model_or_collection, resp, options):当一个model(模型)或collection(集合)的请求远程服务器失败时触发。 "invalid" (model, error, options):当model(模型)在客户端 validation(验证)失败时触发。 "route:[name]" (params):当一个特定route(路由)相匹配时通过路由器触发。 "route" (route, params):当任何一个route(路由)相匹配时通过路由器触发。 "route" (router, route, params):当任何一个route(路由)相匹配时通过history(历史记录)触发。 "all":所有事件发生都能触发这个特别的事件,第一个参数是触发事件的名称。
Backbone 对象中的很多方法都允许传递一个 options 参数。如果不希望方法的执行触发事件,可以传递 { silent: true }
选项:
var model = new Backbone.Model({ age: 19 }); model.on(‘change‘, function(){ alert(‘age changed!‘); }); model.set(‘age‘, 20, { silent: true }); // 不触发 change 事件
Backbone.Model - 数据模型
Backbone.Model 用来封装一个 JSON 对象,通过 attributes 属性引用 JSON 对象:
var Man = Backbone.Model.extend({ defaults:{ name: ‘PXM‘ } }); var man = new Man({ age: 18 }); man.set(‘sex‘, ‘male‘); console.log(man.attributes); // => Object {age: 18, name: "PXM", sex: "male"}
但是应该避免使用 model.attributes.name
的方式直接访问封装的 JSON 对象 。应该使用 set()
和 get()
方法设置和获取 JSON 对象的属性,通过这种方式使得 Backbone 能够监控 JSON 对象的变化:
var model = new Backbone.Model();
model.on(‘change:name‘, function(model,val){
console.log(‘val:‘ + val);
});
model.set(‘age‘, ‘18‘);
model.set(‘name‘, ‘PXM‘); // val:PXM
Backbone.Model 可以很方便的对 JSON 对象的属性值进行校验:
var MyModel = Backbone.Model.extend({ initialize: function(){ this.bind("invalid",function(model,error){ alert(error); }); }, validate: function(attributes) { if (attributes.age < 0) { return "年龄不能小于0"; } } }); var model = new MyModel({name: ‘PXM‘}); model.set(‘age‘, ‘-1‘, {validate: true}); // set()方法默认不校验,只有指定validate选项为true时才会校验 model.save(); // save()方法会自动进行校验
Backbone.Collection - 集合
Backbone.Collection 内部封装了一个数组,通过 models 引用这个数组,这个数组中保存着添加进该集合的所有数据模型实例:
var collection = new Backbone.Collection([{name: ‘旺财‘, age: 8}, {anme: ‘来福‘, age: 18}]); console.log(collection.models); // [B…e.Model, B…e.Model]
与 Backbobe.Model 类似,应该避免使用 collection.models[0]
的方式直接操作内部封装地数组。应该使用 get()/add()/remove()/set()
等方法设置和获取数组的元素,使得 Backbone 能够监控数组的变化:
var collection = new Backbone.Collection(); collection.on(‘add‘, function(){ console.log(‘集合中添加了新的模型...‘); }); collection.add({name: ‘旺财‘, age: 8}); // 集合中添加了新的模型...
如果集合实例拥有 comparator 实例方法,则在进行 add()/remove()/set()
操作结束之后会自动调用 sort()
方法进行一次排序,并且触发一次 sort
事件:
var Girl = Backbone.Model.extend(); var Girls = Backbone.Collection.extend({ model: Girl, comparator: function(model1, model2) { var age1 = model1.get(‘age‘); var age2 = model2.get(‘age‘); if (age1 > age2) { return 1; } else if (age1 === age2) { return 0; } else { return -1; } } }); var girls = new Girls(); girls.on(‘sort‘, function(){ console.log(‘集合重新排序了...‘); }); girls.add([{ // 集合重新排序了... name: ‘小黄‘, age: 20 }, { name: ‘小红‘, age: 18 }, { name: ‘小兰‘, age: 22 }]); console.log(girls.pluck(‘name‘)); // ["小红", "小黄", "小兰"]
Backbone.Router - 路由
Backbone.Router 用来提供路径与 action 的映射关系。路径与路由字符串(或者正则表达式)去匹配,匹配上则调用对应的 action。
路由字符串中包含以下规则:
- :param,匹配到下个
/
之间的字符 - *splat,匹配到路径最后之间的所有字符
- (),表示括号内的字符是可选的
Backbone.Router 实例使用 route()
方法来注册路由字符串与action的映射关系:
var route1 = new Backbone.Router().route(‘(/)‘, ‘index‘, function(){ // route 方法是另一种绑定路由 Action映射关系的方法 console.log(‘index‘); });
在 Backbone.Router 的构造函数中,会遍历 routes 属性,依次调用 route()
方法对 routes 对象中的每一个映射关系进行注册:
var MyRouter = Backbone.Router.extend({ routes: { "(/)": "index", // http://www.example.com -> index() "help(/)": "help", // http://www.example.com/#/help -> help() "search/:keyword(/p:page)(/)": "search", // http://www.example.com/#/search/baidu/p2 -> search(‘baidu‘, 2) "download/*file(/)": "download", // http://www.example.com/#/download/aa/bb.txt -> download(‘aa/bb.txt‘) "*error": "error" // http://www.example.com/#/xxx -> fourOfour(‘xxx‘) }, index: function(){ console.log(‘index‘); }, help: function(){ console.log(‘help‘); }, search: function(keyword, page){ console.log(‘search‘, keyword, page); }, download: function(file){ console.log(‘download‘, file); }, error: function(error) { console.log(‘error‘, error); } }); var router = new MyRouter();
Backbone.Router 实例可以绑定 route:name 事件(其中的 name 是 Action 函数的名字),该事件在 Action 被调用时触发:
router.on(‘route:index‘, function(){ alert(‘进入index了‘); });
Backbone.History - 历史管理和导航
Backbone.history 是 Backbone 全局路由服务,用来监听页面状态的变化,并且触发相应的路由。
Backbone.history 的 handlers 属性引用一个数组,该数组里保存着在当前页面创建的所有的 Backbone.Router 实例中注册的所有路由和 action 的映射关系:
var route1 = new Backbone.Router().route(‘index‘, ‘index‘, function(){ // route 方法是另一种绑定路由 Action映射关系的方法 console.log(‘index‘); }); var route2 = new Backbone.Router().route(‘help‘, ‘help‘, function(){ console.log(‘help‘); }); Array.prototype.forEach.call(Backbone.history.handlers, function(n,i){ console.log(n.route); }); // /^help(?:\?([\s\S]*))?$/ // /^index(?:\?([\s\S]*))?$/
Backbone.history 监控页面状态变化的机制是:
- 浏览器支持pushState,Backbone 将监视 popstate 事件来捕捉页面状态的变化
- 浏览器不支持 pushState,Backbone 将监视 onhashchange 事件以捕捉页面状态的变化
- 浏览器不支持 onhashchange 事件,Backbone 将使用轮询技术定时检测 url 的变化
当页面上所有的 Backbone.Router 实例创建好之后,需要手动调用Backbone.history.start();
去启动 Backbone 对当前页面状态的监听。可以在选项中指定使用 pushState 方式还是 hashChange 方式。
Backbone.history.start({ pushState : true, // 使用 pushState 方法导航,Backbone 将监控 pathname 中相对于 root 路径的相对路径的变化 hashChange: false, // 使用 hashChange 方式导航,Backbone 将监控 hash 的变化 root: "/public/search/" // 应用的根路径,默认 / });
Backbone 对页面状态的监听开启之后,除了用户响应用户操作的被动导航,在应用中还可以调用 router.navigate()
方法主动导航到新的视图。使用该方法默认不会触发路由,并且会保存到浏览器历史记录。可以使用选项来控制这些行为:
router.navigate(‘search/baidu/p2‘, { trigger: true, // 触发路由 replace: true // 不被浏览器记录 });
Backbone.View - 视图
Backbone.View 绑定到一个 DOM 元素上,该视图下所有的事件、渲染全部限制在该 DOM 元素内部进行。Backbone.View 使用 el 属性指定一个选择器表达式:
var view = new Backbone.View({
el: ‘body‘
});
每个 Backbone.View 实例有一个 render() 方法,默认的 render() 方法没有任何的操作。创建一个新的视图实例时,需要重写 render() 方法。在 render() 方法中可以直接操作 DOM 元素来更新视图,也可以使用各种 js 模板渲染引擎来渲染视图。
使用 render() 方法来更新视图的好处是:过绑定视图的 render 函数到模型的 "change" 事件 — 模型数据会即时的显示在 UI 中:
var MyView = Backbone.View.extend({ initialize: function(){ this.listenTo(this.model, "change", this.render); }, render: function(){ this.$el.html(‘Hello,‘+this.model.get(‘name‘)); } }); var view = new MyView({ el: ‘body‘, model: new Backbone.Model({name: ‘PXM‘}) }); view.render(); // view.model.set(‘name‘, ‘XXX‘);
Backbone 不强制依赖 jQuery/Zepto,但是 Backbone.View 的 DOM 操作依赖于 jQuery/Zepto 等类库,因此在使用 Backbone.View 时如果没有引入 jQuery/Zepto 会报错。
每个 Backbone.View 实例拥有一个 $el 属性引用一个视图元素的缓存 jQuery 对象。拥有一个 $ 属性引用一个函数function(selector){ return this.$el.find(selector); }
, 该函数用于在视图内部获取元素。
var MyView = Backbone.View.extend({
initialize: function(){
this.$(‘.red‘).css(‘color‘, ‘red‘); // this.$(‘.red‘) 只获取到本视图内的 .red 元素
}
});
var view = new MyView({
el: ‘#view1‘
});
Backbone 使用 jQuery/Zepto 为视图内的 DOM 元素绑定事件。由于视图内的元素是动态变化的,因此需要在视图元素上代理绑定事件。Backbone.View 使用 delegateEvents() 方法进行代理事件绑定:
var MyView = Backbone.View.extend({
initialize: function(){
this.render();
this.listenTo( this.model, ‘change‘, this.render );
},
render: function(){
this.$el.html(‘Hello,<span>‘ + this.model.get(‘name‘) + "</span>");
}
});
var view = new MyView({
el: ‘#view1‘,
model: new Backbone.Model({‘name‘: ‘PXM‘})
});
view.delegateEvents({"click span": function(){
var name = prompt(‘请输入新的名字:‘);
this.model.set(‘name‘, name || ‘‘);
}});
Backbone.View 在实例化时,会对 events 属性内部的事件自动调用 delegateEvents()
进行代理事件绑定:
var MyView = Backbone.View.extend({
initialize: function() {
this.render();
this.listenTo(this.model, ‘change‘, this.render);
},
render: function() {
this.$el.html(‘Hello,<span>‘ + this.model.get(‘name‘) + "</span>");
},
events: {
‘click span‘: function() {
var name = prompt(‘请输入新的名字:‘);
this.model.set(‘name‘, name || ‘‘);
}
}
});
var view = new MyView({
el: ‘#view1‘,
model: new Backbone.Model({
‘name‘: ‘PXM‘
})
});
源码分析
1 // Backbone.js 1.1.2 2 3 // (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 // Backbone may be freely distributed under the MIT license. 5 // For all details and documentation: 6 // http://backbonejs.org 7 8 (function (root, factory) { 9 10 // AMD 模块化规范兼容 11 // Set up Backbone appropriately for the environment. Start with AMD. 12 if (typeof define === ‘function‘ && define.amd) { 13 define([‘underscore‘, ‘jquery‘, ‘exports‘], function (_, $, exports) { 14 // Export global even in AMD case in case this script is loaded with 15 // others that may still expect a global Backbone. 16 root.Backbone = factory(root, exports, _, $); 17 }); 18 // CMD 模块化规范兼容 19 // Next for Node.js or CommonJS. jQuery may not be needed as a module. 20 } else if (typeof exports !== ‘undefined‘) { 21 var _ = require(‘underscore‘); 22 factory(root, exports, _); 23 // 浏览器全局引用 24 // Finally, as a browser global. 25 } else { 26 root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); 27 } 28 // 依赖 _和$ 29 }(this, function (root, Backbone, _, $) { 30 31 // Initial Setup 32 // ------------- 33 34 // Save the previous value of the `Backbone` variable, so that it can be 35 // restored later on, if `noConflict` is used. 36 var previousBackbone = root.Backbone; 37 38 // Create local references to array methods we‘ll want to use later. 39 var array = []; 40 var slice = array.slice; 41 42 // Current version of the library. Keep in sync with `package.json`. 43 Backbone.VERSION = ‘1.1.2‘; 44 45 // For Backbone‘s purposes, jQuery, Zepto, Ender, or My Library (kidding) owns 46 // the `$` variable. 47 Backbone.$ = $; 48 49 // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable 50 // to its previous owner. Returns a reference to this Backbone object. 51 Backbone.noConflict = function () { 52 root.Backbone = previousBackbone; 53 return this; 54 }; 55 56 // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option 57 // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and 58 // set a `X-Http-Method-Override` header. 59 Backbone.emulateHTTP = false; 60 61 // Turn on `emulateJSON` to support legacy servers that can‘t deal with direct 62 // `application/json` requests ... this will encode the body as 63 // `application/x-www-form-urlencoded` instead and will send the model in a 64 // form param named `model`. 65 Backbone.emulateJSON = false; 66 67 // Backbone.Events 68 // --------------- 69 70 // A module that can be mixed in to *any object* in order to provide it with 71 // custom events. You may bind with `on` or remove with `off` callback 72 // functions to an event; `trigger`-ing an event fires all callbacks in 73 // succession. 74 // 75 // var object = {}; 76 // _.extend(object, Backbone.Events); 77 // object.on(‘expand‘, function(){ alert(‘expanded‘); }); 78 // object.trigger(‘expand‘); 79 // 80 var Events = Backbone.Events = { 81 82 // Bind an event to a `callback` function. Passing `"all"` will bind 83 // the callback to all events fired. 84 on: function (name, callback, context) { 85 // 如果 name 是JSON形式的,那么第二个参数 callback 实际上是 context 86 if (!eventsApi(this, ‘on‘, name, [callback, context]) || !callback) return this; 87 /* 88 this._events 结构: 89 { 90 ‘change‘: [ { callback: ..., context: ..., ctx: ... }, ... ], 91 ‘add‘: [...] 92 } 93 */ 94 this._events || (this._events = {}); 95 var events = this._events[name] || (this._events[name] = []); 96 events.push({ 97 callback: callback, 98 context: context, 99 ctx: context || this 100 }); 101 return this; 102 }, 103 104 // Bind an event to only be triggered a single time. After the first time 105 // the callback is invoked, it will be removed. 106 once: function (name, callback, context) { 107 if (!eventsApi(this, ‘once‘, name, [callback, context]) || !callback) return this; 108 var self = this; 109 // 使用 once 代理绑定的事件回调,执行之前先解绑 110 var once = _.once(function () { 111 self.off(name, once); 112 callback.apply(this, arguments); 113 }); 114 // 代理事件回调中使用 _callback 引用原本的回调,这个将作为 解绑 once() 方法绑定的回调的依据 115 once._callback = callback; 116 return this.on(name, once, context); 117 }, 118 119 // Remove one or many callbacks. If `context` is null, removes all 120 // callbacks with that function. If `callback` is null, removes all 121 // callbacks for the event. If `name` is null, removes all bound 122 // callbacks for all events. 123 off: function (name, callback, context) { 124 if (!this._events || !eventsApi(this, ‘off‘, name, [callback, context])) return this; 125 126 // xxx.off(); -> 解绑所有事件 127 // Remove all callbacks for all events. 128 if (!name && !callback && !context) { 129 this._events = void 0; 130 return this; 131 } 132 133 // 传递了参数 name,直接在 _event.name 数组中找需要解绑的回调函数,然后删除 134 // 没有传递参数 name,需要在 _evnet 下所有的所有的数组中找需要解绑的回调函数,然后删除 135 var names = name ? [name] : _.keys(this._events); 136 for (var i = 0, length = names.length; i < length; i++) { 137 name = names[i]; 138 139 // Bail out if there are no events stored. 140 var events = this._events[name]; 141 if (!events) continue; 142 143 // xxx.off(‘name‘) -> 解绑 name 事件下所有的事件回调 144 // Remove all callbacks for this event. 145 if (!callback && !context) { 146 delete this._events[name]; 147 continue; 148 } 149 150 // Find any remaining events. 151 var remaining = []; 152 for (var j = 0, k = events.length; j < k; j++) { 153 var event = events[j]; 154 if ( 155 callback && callback !== event.callback && 156 callback !== event.callback._callback || 157 context && context !== event.context 158 ) { 159 // _event[name] 数组中与传递参数中的回调不匹配的回调 push 进 remaining 数组 160 remaining.push(event); 161 } 162 } 163 164 // 重新赋值 _event.name 数组。为什么不直接使用 splice() 方法删除匹配的回调? 165 // 可能他觉得直接 splice() 效率太低。。。 166 // Replace events if there are any remaining. Otherwise, clean up. 167 if (remaining.length) { 168 this._events[name] = remaining; 169 } else { 170 delete this._events[name]; 171 } 172 } 173 174 return this; 175 }, 176 177 // Trigger one or many events, firing all bound callbacks. Callbacks are 178 // passed the same arguments as `trigger` is, apart from the event name 179 // (unless you‘re listening on `"all"`, which will cause your callback to 180 // receive the true name of the event as the first argument). 181 trigger: function (name) { 182 if (!this._events) return this; 183 // args -> 除去 name 参数,剩下的参数集合 184 var args = slice.call(arguments, 1); 185 if (!eventsApi(this, ‘trigger‘, name, args)) return this; 186 var events = this._events[name]; 187 // all 也是一个事件名 188 var allEvents = this._events.all; 189 // 触发 非all 事件时,第一个参数 不是 name 190 if (events) triggerEvents(events, args); 191 // 触发 all 事件时第一个参数是 name 192 if (allEvents) triggerEvents(allEvents, arguments); 193 return this; 194 }, 195 196 // Inversion-of-control versions of `on` and `once`. Tell *this* object to 197 // listen to an event in another object ... keeping track of what it‘s 198 // listening to. 199 listenTo: function (obj, name, callback) { 200 // this.__listeningTo 数组保存着 被监听的对象,使用 stopListening() 方法可以解除被监听对象上的事件绑定 201 var listeningTo = this._listeningTo || (this._listeningTo = {}); 202 var id = obj._listenId || (obj._listenId = _.uniqueId(‘l‘)); 203 listeningTo[id] = obj; 204 // name 如果是 JSON 的形式,callback 参数实际上是 context,因此设置为 this 205 if (!callback && typeof name === ‘object‘) callback = this; 206 // 事件绑定到 被监听对象 207 obj.on(name, callback, this); 208 return this; 209 }, 210 211 listenToOnce: function (obj, name, callback) { 212 /* JSON的形式 213 xxx.listenToOnce( obj, { 214 change: function(){ ... }, 215 add: function(){ ... } 216 } ); 217 */ 218 if (typeof name === ‘object‘) { 219 for (var event in name) this.listenToOnce(obj, event, name[event]); 220 return this; 221 } 222 /* name 空格分隔的形式 223 xxx.listenToOnce( obj, ‘change add‘, function(){ ... } ) 224 */ 225 if (eventSplitter.test(name)) { 226 var names = name.split(eventSplitter); 227 for (var i = 0, length = names.length; i < length; i++) { 228 this.listenToOnce(obj, names[i], callback); 229 } 230 return this; 231 } 232 if (!callback) return this; 233 // 使用 once 代理,执行之前先停止监听 234 var once = _.once(function () { 235 this.stopListening(obj, name, once); 236 callback.apply(this, arguments); 237 }); 238 once._callback = callback; 239 return this.listenTo(obj, name, once); 240 }, 241 242 // Tell this object to stop listening to either specific events ... or 243 // to every object it‘s currently listening to. 244 stopListening: function (obj, name, callback) { 245 var listeningTo = this._listeningTo; 246 if (!listeningTo) return this; 247 // stopListening(obj) -> 在listeningTo列表中 删除被监控对象 248 var remove = !name && !callback; 249 // name 如果是 JSON 的形式,callback 参数实际上是 context,因此设置为 this 250 if (!callback && typeof name === ‘object‘) callback = this; 251 // listeningTo( obj, name, callback ) -> 只解除监听指定对象上的事件 252 if (obj)(listeningTo = {})[obj._listenId] = obj; 253 // listeningTo(null, name, callback ) -> 解除 被监听对象列表 _listeningTo 中所有被监听对象上的 事件 254 for (var id in listeningTo) { 255 obj = listeningTo[id]; 256 obj.off(name, callback, this); 257 // stopListening(obj) -> 在listeningTo列表中 删除被监控对象 258 if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id]; 259 } 260 return this; 261 } 262 263 }; 264 265 // Regular expression used to split event strings. 266 var eventSplitter = /\s+/; 267 268 // Implement fancy features of the Events API such as multiple event 269 // names `"change blur"` and jQuery-style event maps `{change: action}` 270 // in terms of the existing API. 271 var eventsApi = function (obj, action, name, rest) { 272 if (!name) return true; 273 274 /* JSON 形式 275 model.on({ 276 ‘change:name‘: function(){ 277 console.log(‘change:name fired‘); 278 }, 279 ‘change:age‘: function(){ 280 console.log(‘change:age fired‘); 281 } 282 }); 283 */ 284 // Handle event maps. 285 if (typeof name === ‘object‘) { 286 for (var key in name) { 287 // obj.on( name, callback, context ) 288 obj[action].apply(obj, [key, name[key]].concat(rest)); 289 } 290 return false; 291 } 292 293 /* 空格分割形式: 294 model.on( ‘change:name change:age‘, function(){ 295 console.log( ‘change:name or change:age fired‘ ); 296 } ); 297 */ 298 // Handle space separated event names. 299 if (eventSplitter.test(name)) { 300 var names = name.split(eventSplitter); 301 for (var i = 0, length = names.length; i < length; i++) { 302 obj[action].apply(obj, [names[i]].concat(rest)); 303 } 304 return false; 305 } 306 307 return true; 308 }; 309 310 // 理解不了,难道使用call比apply更快??? 311 // A difficult-to-believe, but optimized internal dispatch function for 312 // triggering events. Tries to keep the usual cases speedy (most internal 313 // Backbone events have 3 arguments). 314 var triggerEvents = function (events, args) { 315 var ev, i = -1, 316 l = events.length, 317 a1 = args[0], 318 a2 = args[1], 319 a3 = args[2]; 320 switch (args.length) { 321 case 0: 322 while (++i < l)(ev = events[i]).callback.call(ev.ctx); 323 return; 324 case 1: 325 while (++i < l)(ev = events[i]).callback.call(ev.ctx, a1); 326 return; 327 case 2: 328 while (++i < l)(ev = events[i]).callback.call(ev.ctx, a1, a2); 329 return; 330 case 3: 331 while (++i < l)(ev = events[i]).callback.call(ev.ctx, a1, a2, a3); 332 return; 333 default: 334 while (++i < l)(ev = events[i]).callback.apply(ev.ctx, args); 335 return; 336 } 337 }; 338 339 // Aliases for backwards compatibility. 340 Events.bind = Events.on; 341 Events.unbind = Events.off; 342 343 // Allow the `Backbone` object to serve as a global event bus, for folks who 344 // want global "pubsub" in a convenient place. 345 _.extend(Backbone, Events); 346 347 // Backbone.Model 348 // -------------- 349 350 // Backbone **Models** are the basic data object in the framework -- 351 // frequently representing a row in a table in a database on your server. 352 // A discrete chunk of data and a bunch of useful, related methods for 353 // performing computations and transformations on that data. 354 355 // Create a new model with the specified attributes. A client id (`cid`) 356 // is automatically generated and assigned for you. 357 var Model = Backbone.Model = function (attributes, options) { 358 var attrs = attributes || {}; 359 options || (options = {}); 360 this.cid = _.uniqueId(‘c‘); // 每个Model实例获得一个唯一标识 361 this.attributes = {}; 362 // options.collecton 的作用??? 363 if (options.collection) this.collection = options.collection; 364 // option.parse 的作用??? 365 if (options.parse) attrs = this.parse(attrs, options) || {}; 366 // 合并 实例属性 defaults 的值或者 实例方法 defaults 的执行结果 367 attrs = _.defaults({}, attrs, _.result(this, ‘defaults‘)); 368 // 构造函数中是调用 set() 方法进行设值, set() 方法是循环拷贝 attr 中的每一个属性,因此避免在外部修改 attr 对象对模型数据产生的影响 369 this.set(attrs, options); 370 // 用来保存上次 change 事件触发时 改变的属性集合 371 this.changed = {}; 372 // 调用实例的 initialize 方法完成一些初始化工作 373 this.initialize.apply(this, arguments); 374 }; 375 376 // Events -> Model.prototype -- 拷贝继承 377 // Model.prototype -> model -- 原型继承 378 // => 结果是 model 拥有了 Events 对象中的 on/off/listenTo 等方法 379 // Attach all inheritable methods to the Model prototype. 380 _.extend(Model.prototype, Events, { 381 382 // 上次 change 事件发生时,修改的属性集合 383 // A hash of attributes whose current and previous value differ. 384 changed: null, 385 386 // 上次检验错误信息 387 // The value returned during the last failed validation. 388 validationError: null, 389 390 // 主键字段,如学生模型,可以指定学号字段为主键字段 391 // The default name for the JSON `id` attribute is `"id"`. MongoDB and 392 // CouchDB users may want to set this to `"_id"`. 393 idAttribute: ‘id‘, 394 395 // Initialize is an empty function by default. Override it with your own 396 // initialization logic. 397 initialize: function () {}, 398 399 // Return a copy of the model‘s `attributes` object. 400 toJSON: function (options) { 401 // 这里返回了一个 model 内封装地 pojo 对象的一个拷贝,但是需要注意:_.clone()方法不会进行深拷贝,因此当 pojo 对象的某些属性值 是 对象时,在外部对这个对象进行修改可能会影响到 pojo 402 return _.clone(this.attributes); 403 }, 404 405 // 同步方法,默认使用全局的 Backbone.sync 方法,Backbone.sync 方法默认调用 $.ajax 发送 REST 风格的HTTP请求 406 // Proxy `Backbone.sync` by default -- but override this if you need 407 // custom syncing semantics for *this* particular model. 408 sync: function () { 409 return Backbone.sync.apply(this, arguments); 410 }, 411 412 // Get the value of an attribute. 413 get: function (attr) { 414 // get() 方法如果返回的是一个对象,可能导致 pojo 在外部被更改 415 return this.attributes[attr]; 416 }, 417 418 // 获取属性并进行 escape() 转移 419 // Get the HTML-escaped value of an attribute. 420 escape: function (attr) { 421 return _.escape(this.get(attr)); 422 }, 423 424 // Returns `true` if the attribute contains a value that is not null 425 // or undefined. 426 has: function (attr) { 427 return this.get(attr) != null; 428 }, 429 430 // Special-cased proxy to underscore‘s `_.matches` method. 431 matches: function (attrs) { 432 // _.matches/matcher() 接受一个json返回一个函数,这个函数用于判断对象是否和json中的键值对匹配 433 return _.matches(attrs)(this.attributes); 434 }, 435 436 // Set a hash of model attributes on the object, firing `"change"`. This is 437 // the core primitive operation of a model, updating the data and notifying 438 // anyone who needs to know about the change in state. The heart of the beast. 439 // options 中主要有两个: 440 // 1: silent - 不触发 change 事件 441 // 2: unset - 参数中的属性都会在 attributes 中删除 442 set: function (key, val, options) { 443 var attr, attrs, unset, changes, silent, changing, prev, current; 444 if (key == null) return this; 445 446 // Handle both `"key", value` and `{key: value}` -style arguments. 447 if (typeof key === ‘object‘) { 448 attrs = key; 449 // JSON形式的参数,第二个参数是 options 450 options = val; 451 } else { 452 (attrs = {})[key] = val; 453 } 454 455 options || (options = {}); 456 457 // 校验 458 // Run validation. 459 // this._validate() 返回 false, 则直接返回false,不执行后面代码 460 if (!this._validate(attrs, options)) return false; 461 462 // Extract attributes and options. 463 unset = options.unset; 464 silent = options.silent; 465 466 // changes 用来保存本次 set() 操作需要更新的字段名 467 changes = []; 468 469 // 保存原来的 _changing 值 470 changing = this._changing; 471 // 开始设值时:this._changing 值设置为 true 472 this._changing = true; 473 474 // 如果不是 “正在改变中”,克隆一份 attributes 475 // 如果是 “正在改变中”,这种情况下是在 change 事件处理函数中调用了 set() 方法,只能算同一个过程,因此不能拷贝一个全新的 attributes,而是继续使用原来的 _previousAttributes 476 if (!changing) { 477 this._previousAttributes = _.clone(this.attributes); 478 this.changed = {}; 479 } 480 // current 引用当前的 attributes, prev引用当前 attributes 的一个克隆 481 // 第一次调用 set() 进来,current 和 prev 值相同 482 // 在 change 事件中调用 set() 进来,由于 current 中的部分属性可能已经改变。而 prev 值还是外层 set() 调用之前的属性值 483 current = this.attributes, prev = this._previousAttributes; 484 485 // 如果set()中改变id属性,则模型的 id 也更新 486 // Check for changes of `id`. 487 if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; 488 489 // For each `set` attribute, update or delete the current value. 490 for (attr in attrs) { 491 val = attrs[attr]; 492 // 需要更新的字段名进入 changes 数组 493 if (!_.isEqual(current[attr], val)) changes.push(attr); 494 // this.changed -> 保存着已经修改的属性 495 if (!_.isEqual(prev[attr], val)) { 496 this.changed[attr] = val; 497 } else { 498 // 如果外层 set() 修改了一个属性值,内层 set() 又改为了原值,则整个设值过程中,attr并没有改变,所以要删除 499 delete this.changed[attr]; 500 } 501 // 设置了 unset 选项,则 set() 参数中的属性都会被删除 502 unset ? delete current[attr] : current[attr] = val; 503 } 504 // silent 选项可以避免触发事件 505 // 触发 change:name 事件 506 // Trigger all relevant attribute changes. 507 if (!silent) { 508 if (changes.length) this._pending = options; 509 for (var i = 0, length = changes.length; i < length; i++) { 510 // 第一个参数是 model,第二参数是 改变之后的值 511 this.trigger(‘change:‘ + changes[i], this, current[changes[i]], options); 512 } 513 } 514 515 // You might be wondering why there‘s a `while` loop here. Changes can 516 // be recursively nested within `"change"` events. 517 // 注意:在 change 事件处理函数中嵌套调用 set() 方法,则直接返回,不会触发 change 事件。只有在最外层 set() 方法中才会触发 change 事件。 518 if (changing) return this; 519 if (!silent) { 520 // 使用 while 循环,是因为 可能在 change 事件处理程序中调用 set() 方法。由于嵌套 set() 中不会触发 change 事件,因此需要在这里触发多次 521 while (this._pending) { 522 options = this._pending; 523 this._pending = false; 524 this.trigger(‘change‘, this, options); 525 } 526 } 527 this._pending = false; 528 // 结束设值时:this._changing 值设置为 false 529 this._changing = false; 530 return this; 531 }, 532 533 // Remove an attribute from the model, firing `"change"`. `unset` is a noop 534 // if the attribute doesn‘t exist. 535 unset: function (attr, options) { 536 return this.set(attr, void 0, _.extend({}, options, { 537 unset: true 538 })); 539 }, 540 541 // Clear all attributes on the model, firing `"change"`. 542 clear: function (options) { 543 var attrs = {}; 544 for (var key in this.attributes) attrs[key] = void 0; 545 return this.set(attrs, _.extend({}, options, { 546 unset: true 547 })); 548 }, 549 550 // Determine if the model has changed since the last `"change"` event. 551 // If you specify an attribute name, determine if that attribute has changed. 552 hasChanged: function (attr) { 553 // this.changed 保存着自上次 change 事件触发时修改的属性值。 554 // 之所以说 是上次 change 事件触发而不是上次调用 set() 方法,是因为在 change:name 事件中调用的set()方法中不回更新 this.changed 对象 555 if (attr == null) return !_.isEmpty(this.changed); 556 return _.has(this.changed, attr); 557 }, 558 559 // Return an object containing all the attributes that have changed, or 560 // false if there are no changed attributes. Useful for determining what 561 // parts of a view need to be updated and/or what attributes need to be 562 // persisted to the server. Unset attributes will be set to undefined. 563 // You can also pass an attributes object to diff against the model, 564 // determining if there *would be* a change. 565 changedAttributes: function (diff) { 566 // 没有传 diff 参数,则直接返回 this.changed 567 if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; 568 var val, changed = false; 569 var old = this._changing ? this._previousAttributes : this.attributes; 570 for (var attr in diff) { 571 // 传了 diff 参数,则返回与 diff 对象中与 _previousAttributes 不同的属性 572 if (_.isEqual(old[attr], (val = diff[attr]))) continue; 573 (changed || (changed = {}))[attr] = val; 574 } 575 return changed; 576 }, 577 578 // 获取之前的某个属性值,不过当 attr 参数为空时,返回整个 _previousAttributes 对象 579 // Get the previous value of an attribute, recorded at the time the last 580 // `"change"` event was fired. 581 previous: function (attr) { 582 if (attr == null || !this._previousAttributes) return null; 583 return this._previousAttributes[attr]; 584 }, 585 586 // 获取之前的整个 _previousAttributes 对象 587 // Get all of the attributes of the model at the time of the previous 588 // `"change"` event. 589 previousAttributes: function () { 590 return _.clone(this._previousAttributes); 591 }, 592 593 // Fetch the model from the server. If the server‘s representation of the 594 // model differs from its current attributes, they will be overridden, 595 // triggering a `"change"` event. 596 fetch: function (options) { 597 options = options ? _.clone(options) : {}; 598 if (options.parse === void 0) options.parse = true; 599 var model = this; 600 var success = options.success; 601 options.success = function (resp) { 602 if (!model.set(model.parse(resp, options), options)) return false; 603 if (success) success(model, resp, options); 604 model.trigger(‘sync‘, model, resp, options); 605 }; 606 wrapError(this, options); 607 return this.sync(‘read‘, this, options); 608 }, 609 610 // Set a hash of model attributes, and sync the model to the server. 611 // If the server returns an attributes hash that differs, the model‘s 612 // state will be `set` again. 613 save: function (key, val, options) { 614 var attrs, method, xhr, attributes = this.attributes; 615 616 // Handle both `"key", value` and `{key: value}` -style arguments. 617 if (key == null || typeof key === ‘object‘) { 618 attrs = key; 619 options = val; 620 } else { 621 (attrs = {})[key] = val; 622 } 623 624 options = _.extend({ 625 validate: true 626 }, options); 627 628 // If we‘re not waiting and attributes exist, save acts as 629 // `set(attr).save(null, opts)` with validation. Otherwise, check if 630 // the model will be valid when the attributes, if any, are set. 631 if (attrs && !options.wait) { 632 if (!this.set(attrs, options)) return false; 633 } else { 634 if (!this._validate(attrs, options)) return false; 635 } 636 637 // Set temporary attributes if `{wait: true}`. 638 if (attrs && options.wait) { 639 this.attributes = _.extend({}, attributes, attrs); 640 } 641 642 // After a successful server-side save, the client is (optionally) 643 // updated with the server-side state. 644 if (options.parse === void 0) options.parse = true; 645 var model = this; 646 var success = options.success; 647 options.success = function (resp) { 648 // Ensure attributes are restored during synchronous saves. 649 model.attributes = attributes; 650 var serverAttrs = model.parse(resp, options); 651 if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); 652 if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { 653 return false; 654 } 655 if (success) success(model, resp, options); 656 model.trigger(‘sync‘, model, resp, options); 657 }; 658 wrapError(this, options); 659 660 method = this.isNew() ? ‘create‘ : (options.patch ? ‘patch‘ : ‘update‘); 661 if (method === ‘patch‘ && !options.attrs) options.attrs = attrs; 662 xhr = this.sync(method, this, options); 663 664 // Restore attributes. 665 if (attrs && options.wait) this.attributes = attributes; 666 667 return xhr; 668 }, 669 670 // Destroy this model on the server if it was already persisted. 671 // Optimistically removes the model from its collection, if it has one. 672 // If `wait: true` is passed, waits for the server to respond before removal. 673 destroy: function (options) { 674 options = options ? _.clone(options) : {}; 675 var model = this; 676 var success = options.success; 677 678 var destroy = function () { 679 model.stopListening(); 680 model.trigger(‘destroy‘, model, model.collection, options); 681 }; 682 683 options.success = function (resp) { 684 if (options.wait || model.isNew()) destroy(); 685 if (success) success(model, resp, options); 686 if (!model.isNew()) model.trigger(‘sync‘, model, resp, options); 687 }; 688 689 if (this.isNew()) { 690 options.success(); 691 return false; 692 } 693 wrapError(this, options); 694 695 var xhr = this.sync(‘delete‘, this, options); 696 if (!options.wait) destroy(); 697 return xhr; 698 }, 699 700 // Default URL for the model‘s representation on the server -- if you‘re 701 // using Backbone‘s restful methods, override this to change the endpoint 702 // that will be called. 703 url: function () { 704 var base = 705 _.result(this, ‘urlRoot‘) || 706 _.result(this.collection, ‘url‘) || 707 urlError(); 708 if (this.isNew()) return base; 709 return base.replace(/([^\/])$/, ‘$1/‘) + encodeURIComponent(this.id); 710 }, 711 712 // **parse** converts a response into the hash of attributes to be `set` on 713 // the model. The default implementation is just to pass the response along. 714 parse: function (resp, options) { 715 return resp; 716 }, 717 718 // 返回一个新的模型对象 719 // Create a new model with identical attributes to this one. 720 clone: function () { 721 return new this.constructor(this.attributes); 722 }, 723 724 // A model is new if it has never been saved to the server, and lacks an id. 725 isNew: function () { 726 return !this.has(this.idAttribute); 727 }, 728 // 是否校验成功,这里是调用一次校验方法得到结果。 729 // Check if the model is currently in a valid state. 730 isValid: function (options) { 731 return this._validate({}, _.extend(options || {}, { 732 validate: true 733 })); 734 }, 735 736 // Run validation against the next complete set of model attributes, 737 // returning `true` if all is well. Otherwise, fire an `"invalid"` event. 738 _validate: function (attrs, options) { 739 // validate 选项不为true,则跳过检验,直接返回true 740 // this.validate 是实例的 validate 方法 741 if (!options.validate || !this.validate) return true; 742 attrs = _.extend({}, this.attributes, attrs); 743 // 如果 this.validate 方法中校验失败,则需要返回一个值 744 var error = this.validationError = this.validate(attrs, options) || null; 745 // 判断 this.validate() 的返回值,返回值为空则校验成功 746 if (!error) return true; 747 // 否则校验失败,触发 invalid 事件(注意:invalid:name 事件不存在) 748 this.trigger(‘invalid‘, this, error, _.extend(options, { 749 validationError: error 750 })); 751 return false; 752 } 753 754 }); 755 756 // 这写都是 underscore 的对象相关方法: 757 // keys 获取对象所有可枚举属性的集合 758 // values 获取对象所有可枚举属性值的集合 759 // pairs 将对象转化成为 [key, value] 形式的数组 760 // invert 将对象的 key 和 value 对换,必须保证 value 值唯一 761 // pick 将对象中的指定 key 的属性选取出来作为一个新的对象 762 // omit 与pick相反,将对象中的除指定 key 以外的属性选取出来作为一个新的对象 763 // chain 返回一个封装的对象. 在封装的对象上调用方法会返回封装的对象本身, 直道 value 方法调用为止. 764 // isEmpty 判断对象是否不包含任何属性 765 // Underscore methods that we want to implement on the Model. 766 var modelMethods = [‘keys‘, ‘values‘, ‘pairs‘, ‘invert‘, ‘pick‘, ‘omit‘, ‘chain‘, ‘isEmpty‘]; 767 768 // Mix in each Underscore method as a proxy to `Model#attributes`. 769 _.each(modelMethods, function (method) { 770 if (!_[method]) return; 771 Model.prototype[method] = function () { 772 // arguments 是一个类数组对象,通过 slice() 方法转变我真的数组 773 var args = slice.call(arguments); 774 // 将 this.attributes 插入最前面 775 args.unshift(this.attributes); 776 // 调用 _ 的对应方法 777 return _[method].apply(_, args); 778 }; 779 }); 780 781 // Backbone.Collection 782 // ------------------- 783 784 // If models tend to represent a single row of data, a Backbone Collection is 785 // more analogous to a table full of data ... or a small slice or page of that 786 // table, or a collection of rows that belong together for a particular reason 787 // -- all of the messages in this particular folder, all of the documents 788 // belonging to this particular author, and so on. Collections maintain 789 // indexes of their models, both in order, and for lookup by `id`. 790 791 // Create a new **Collection**, perhaps to contain a specific type of `model`. 792 // If a `comparator` is specified, the Collection will maintain 793 // its models in sort order, as they‘re added and removed. 794 var Collection = Backbone.Collection = function (models, options) { 795 options || (options = {}); 796 if (options.model) this.model = options.model; 797 if (options.comparator !== void 0) this.comparator = options.comparator; 798 this._reset(); 799 this.initialize.apply(this, arguments); 800 if (models) this.reset(models, _.extend({ 801 silent: true 802 }, options)); 803 }; 804 805 // Default options for `Collection#set`. 806 var setOptions = { 807 add: true, 808 remove: true, 809 merge: true 810 }; 811 var addOptions = { 812 add: true, 813 remove: false 814 }; 815 816 // Define the Collection‘s inheritable methods. 817 _.extend(Collection.prototype, Events, { 818 819 // model 属性引用该 Collection 存储的模型类型的构造函数,默认是 Model,可以通过 extend 方法扩展 Model 类 820 // The default model for a collection is just a **Backbone.Model**. 821 // This should be overridden in most cases. 822 model: Model, 823 824 // Initialize is an empty function by default. Override it with your own 825 // initialization logic. 826 initialize: function () {}, 827 828 // The JSON representation of a Collection is an array of the 829 // models‘ attributes. 830 toJSON: function (options) { 831 return this.map(function (model) { 832 // 调用数组中各个model的toJSON()方法转换成JSON 833 return model.toJSON(options); 834 }); 835 }, 836 837 // Proxy `Backbone.sync` by default. 838 sync: function () { 839 return Backbone.sync.apply(this, arguments); 840 }, 841 842 // Add a model, or list of models to the set. 843 add: function (models, options) { 844 /* 845 options = { 846 merge: false 847 add: true, 848 remove: false 849 } 850 */ 851 return this.set(models, _.extend({ 852 merge: false 853 }, options, addOptions)); 854 }, 855 856 // Remove a model, or a list of models from the set. 857 remove: function (models, options) { 858 var singular = !_.isArray(models); 859 models = singular ? [models] : _.clone(models); 860 options || (options = {}); 861 for (var i = 0, length = models.length; i < length; i++) { 862 // 根据 id 获取 collection.models 中的 model 863 var model = models[i] = this.get(models[i]); 864 // 不存在 model则不进行任何操作 865 if (!model) continue; 866 // 删除 _byId 索引中的记录 867 var id = this.modelId(model.attributes); 868 if (id != null) delete this._byId[id]; 869 delete this._byId[model.cid]; 870 // 删除 collection.models 中的 model 871 var index = this.indexOf(model); 872 this.models.splice(index, 1); 873 this.length--; 874 // 触发 model 的 remove 事件 875 if (!options.silent) { 876 options.index = index; 877 model.trigger(‘remove‘, model, this, options); 878 } 879 // 删除 model 与 collection 的关联 880 this._removeReference(model, options); 881 } 882 return singular ? models[0] : models; 883 }, 884 885 // Update a collection by `set`-ing a new list of models, adding new ones, 886 // removing models that are no longer present, and merging models that 887 // already exist in the collection, as necessary. Similar to **Model#set**, 888 // the core operation for updating the data contained by the collection. 889 set: function (models, options) { 890 options = _.defaults({}, options, setOptions); 891 if (options.parse) models = this.parse(models, options); 892 // 不是数组或者类数组 893 var singular = !_.isArray(models); 894 // 不是类数组,则包装成数组;是数组,转换成真数组 895 models = singular ? (models ? [models] : []) : models.slice(); 896 var id, model, attrs, existing, sort; 897 var at = options.at; 898 if (at != null) at = +at; 899 if (at < 0) at += this.length + 1; 900 var sortable = this.comparator && (at == null) && options.sort !== false; 901 // this,comparator 可以是字符串 902 var sortAttr = _.isString(this.comparator) ? this.comparator : null; 903 var toAdd = [], 904 toRemove = [], 905 modelMap = {}; 906 var add = options.add, 907 merge = options.merge, 908 remove = options.remove; 909 var order = !sortable && add && remove ? [] : false; 910 var orderChanged = false; 911 912 // Turn bare objects into model references, and prevent invalid models 913 // from being added. 914 // 遍历需要处理的模型列表 915 for (var i = 0, length = models.length; i < length; i++) { 916 attrs = models[i]; 917 918 // If a duplicate is found, prevent it from being added and 919 // optionally merge it into the existing model. 920 // get() 方法根据 this._byId对象返回对应的 model,该集合没有该 model 则返回 undefined 921 if (existing = this.get(attrs)) { 922 if (remove) modelMap[existing.cid] = true; 923 if (merge && attrs !== existing) { 924 attrs = this._isModel(attrs) ? attrs.attributes : attrs; 925 if (options.parse) attrs = existing.parse(attrs, options); 926 existing.set(attrs, options); 927 if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; 928 } 929 models[i] = existing; 930 931 // If this is a new, valid model, push it to the `toAdd` list. 932 } else if (add) { 933 // _prepareModel() 接受一个 模型对象或者模型属性对象,返回一个模型对象 934 // -> 在这里,model.collection 指向 this,模型和集合发生关系 935 model = models[i] = this._prepareModel(attrs, options); 936 if (!model) continue; 937 // 将 model 推进 toAdd数组 938 toAdd.push(model); 939 // 通过_byId[cid/id] 可以索引到model,并且对 model 绑定事件 940 this._addReference(model, options); 941 } 942 943 // Do not add multiple models with the same `id`. 944 // model 要么是通过 get() 方法获取的 ,要么是通过 _prepareModel() 方法生成的 model 945 model = existing || model; 946 if (!model) continue; 947 id = this.modelId(model.attributes); 948 if (order && (model.isNew() || !modelMap[id])) { 949 order.push(model); 950 951 // Check to see if this is actually a new model at this index. 952 orderChanged = orderChanged || !this.models[i] || model.cid !== this.models[i].cid; 953 } 954 955 modelMap[id] = true; 956 } 957 958 // Remove nonexistent models if appropriate. 959 if (remove) { 960 for (var i = 0, length = this.length; i < length; i++) { 961 if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); 962 } 963 if (toRemove.length) this.remove(toRemove, options); 964 } 965 966 // See if sorting is needed, update `length` and splice in new models. 967 if (toAdd.length || orderChanged) { 968 // 在添加进集合之后进行排序 969 if (sortable) sort = true; 970 this.length += toAdd.length; 971 if (at != null) { 972 // at 不为空时,将 toAdd 数组插入到 this.models 数组的指定位置处 973 for (var i = 0, length = toAdd.length; i < length; i++) { 974 this.models.splice(at + i, 0, toAdd[i]); 975 } 976 } else { 977 // 否则,将 toAdd 插入到 this.models 数组的后面 978 // order - 清空 this.models 979 if (order) this.models.length = 0; 980 var orderedModels = order || toAdd; 981 for (var i = 0, length = orderedModels.length; i < length; i++) { 982 this.models.push(orderedModels[i]); 983 } 984 } 985 } 986 987 // Silently sort the collection if appropriate. 988 if (sort) this.sort({ 989 // sort() 方法中不触发 sort 事件,则后面代码中 触发了 add 事件之后再触发 sort 事件 990 silent: true 991 }); 992 993 // Unless silenced, it‘s time to fire all appropriate add/sort events. 994 // silence 不为 true,则需要触发每个 model 的 add 事件 995 if (!options.silent) { 996 var addOpts = at != null ? _.clone(options) : options; 997 for (var i = 0, length = toAdd.length; i < length; i++) { 998 if (at != null) addOpts.index = at + i; 999 // 在事件处理函数中可以是用 addOpts.indes 新增的模型在 数组中 的索引 1000 // add 事件是模型的 1001 (model = toAdd[i]).trigger(‘add‘, model, this, addOpts); 1002 } 1003 // 如果排序了,需要触发 collection 的 sort 事件 1004 if (sort || orderChanged) this.trigger(‘sort‘, this, options); 1005 } 1006 1007 // Return the added (or merged) model (or models). 1008 return singular ? models[0] : models; 1009 }, 1010 1011 // When you have more items than you want to add or remove individually, 1012 // you can reset the entire set with a new list of models, without firing 1013 // any granular `add` or `remove` events. Fires `reset` when finished. 1014 // Useful for bulk operations and optimizations. 1015 reset: function (models, options) { 1016 options = options ? _.clone(options) : {}; 1017 for (var i = 0, length = this.models.length; i < length; i++) { 1018 // 删除每个 model.collection,解除每个 model 上的事件绑定 1019 this._removeReference(this.models[i], options); 1020 } 1021 // options.previousModels 保存reset之前的model集合 1022 options.previousModels = this.models; 1023 // 清空 models 1024 this._reset(); 1025 // 调用 add() 方法将 models 添加进 collection 1026 models = this.add(models, _.extend({ 1027 silent: true 1028 }, options)); 1029 // 触发 reset 事件 1030 if (!options.silent) this.trigger(‘reset‘, this, options); 1031 return models; 1032 }, 1033 1034 // Add a model to the end of the collection. 1035 push: function (model, options) { 1036 return this.add(model, _.extend({ 1037 at: this.length 1038 }, options)); 1039 }, 1040 1041 // Remove a model from the end of the collection. 1042 pop: function (options) { 1043 var model = this.at(this.length - 1); 1044 this.remove(model, options); 1045 return model; 1046 }, 1047 1048 // Add a model to the beginning of the collection. 1049 unshift: function (model, options) { 1050 return this.add(model, _.extend({ 1051 at: 0 1052 }, options)); 1053 }, 1054 1055 // Remove a model from the beginning of the collection. 1056 shift: function (options) { 1057 var model = this.at(0); 1058 this.remove(model, options); 1059 return model; 1060 }, 1061 1062 // Slice out a sub-array of models from the collection. 1063 slice: function () { 1064 return slice.apply(this.models, arguments); 1065 }, 1066 1067 // Get a model from the set by id. 1068 get: function (obj) { 1069 if (obj == null) return void 0; 1070 // 获取id 1071 var id = this.modelId(this._isModel(obj) ? obj.attributes : obj); 1072 // obj 可以是 model 的 id / cid / model / model的attr 1073 return this._byId[obj] || this._byId[id] || this._byId[obj.cid]; 1074 }, 1075 1076 // Get the model at the given index. 1077 at: function (index) { 1078 // 负数的支持,coll.at(-1) 表示倒数第一个元素,即最后一个元素 1079 if (index < 0) index += this.length; 1080 return this.models[index]; 1081 }, 1082 1083 // Return models with matching attributes. Useful for simple cases of 1084 // `filter`. 1085 // 返回与指定 attr 属性值匹配的第一个 model 或者 所有 model 组成的集合 1086 where: function (attrs, first) { 1087 var matches = _.matches(attrs); 1088 return this[first ? ‘find‘ : ‘filter‘](function (model) { 1089 return matches(model.attributes); 1090 }); 1091 }, 1092 1093 // Return the first model with matching attributes. Useful for simple cases 1094 // of `find`. 1095 // 返回与指定 attr 属性匹配的第一个 model 1096 findWhere: function (attrs) { 1097 return this.where(attrs, true); 1098 }, 1099 1100 // Force the collection to re-sort itself. You don‘t need to call this under 1101 // normal circumstances, as the set will maintain sort order as each item 1102 // is added. 1103 // 对 collection.models 中的数组进行一次排序,触发 sort 事件 1104 sort: function (options) { 1105 if (!this.comparator) throw new Error(‘Cannot sort a set without a comparator‘); 1106 options || (options = {}); 1107 1108 // Run sort based on type of `comparator`. 1109 if (_.isString(this.comparator) || this.comparator.length === 1) { 1110 // 如果是字符串,则调用 _.sortBy() 方法按照指定的属性进行排序 1111 this.models = this.sortBy(this.comparator, this); 1112 } else { 1113 // 如果 this.comparator 是函数,直接调数组的原生 sort() 方法进行排序 1114 this.models.sort(_.bind(this.comparator, this)); 1115 } 1116 // 触发 sort 事件 1117 if (!options.silent) this.trigger(‘sort‘, this, options); 1118 return this; 1119 }, 1120 1121 // Pluck an attribute from each model in the collection. 1122 pluck: function (attr) { 1123 // 对数组中的每个元素调用 get(attr) 方法,并且将返回的结果作为数组返回 1124 return _.invoke(this.models, ‘get‘, attr); 1125 }, 1126 1127 // Fetch the default set of models for this collection, resetting the 1128 // collection when they arrive. If `reset: true` is passed, the response 1129 // data will be passed through the `reset` method instead of `set`. 1130 fetch: function (options) { 1131 options = options ? _.clone(options) : {}; 1132 if (options.parse === void 0) options.parse = true; 1133 var success = options.success; 1134 var collection = this; 1135 options.success = function (resp) { 1136 var method = options.reset ? ‘reset‘ : ‘set‘; 1137 collection[method](resp, options); 1138 if (success) success(collection, resp, options); 1139 collection.trigger(‘sync‘, collection, resp, options); 1140 }; 1141 wrapError(this, options); 1142 return this.sync(‘read‘, this, options); 1143 }, 1144 1145 // Create a new instance of a model in this collection. Add the model to the 1146 // collection immediately, unless `wait: true` is passed, in which case we 1147 // wait for the server to agree. 1148 create: function (model, options) { 1149 options = options ? _.clone(options) : {}; 1150 if (!(model = this._prepareModel(model, options))) return false; 1151 // 没有设置 options.wait 选项,则 直接添加进集合 1152 if (!options.wait) this.add(model, options); 1153 var collection = this; 1154 var success = options.success; 1155 options.success = function (model, resp) { 1156 if (options.wait) collection.add(model, options); 1157 if (success) success(model, resp, options); 1158 }; 1159 model.save(null, options); 1160 return model; 1161 }, 1162 1163 // **parse** converts a response into a list of models to be added to the 1164 // collection. The default implementation is just to pass it through. 1165 parse: function (resp, options) { 1166 return resp; 1167 }, 1168 1169 // Create a new collection with an identical list of models as this one. 1170 clone: function () { 1171 return new this.constructor(this.models, { 1172 model: this.model, 1173 comparator: this.comparator 1174 }); 1175 }, 1176 1177 // Define how to uniquely identify models in the collection. 1178 // 获取 模型属性对象 中的id 1179 modelId: function (attrs) { 1180 return attrs[this.model.prototype.idAttribute || ‘id‘]; 1181 }, 1182 1183 // Private method to reset all internal state. Called when the collection 1184 // is first initialized or reset. 1185 _reset: function () { 1186 // 清空 models 1187 this.length = 0; 1188 this.models = []; 1189 this._byId = {}; 1190 }, 1191 1192 // Prepare a hash of attributes (or other model) to be added to this 1193 // collection. 1194 _prepareModel: function (attrs, options) { 1195 if (this._isModel(attrs)) { 1196 // attrs 本来就是一个模型对象,则将 model.collection 指向本对象,直接返回 1197 if (!attrs.collection) attrs.collection = this; 1198 return attrs; 1199 } 1200 options = options ? _.clone(options) : {}; 1201 options.collection = this; 1202 // 调用 this.model 构造函数创建一个模型,并且 model.collection 指向本 collection 1203 var model = new this.model(attrs, options); 1204 // 如果 options 中设置了 validate: true,则 this.model -> set() -> _validate(),验证错误信息保存在 model.validationError 中 1205 // 检验成功,则直接返回新创建的模型对象 1206 if (!model.validationError) return model; 1207 // 校验失败,则触发 collection 的 invalid 事件,并返回 false 1208 this.trigger(‘invalid‘, this, model.validationError, options); 1209 return false; 1210 }, 1211 1212 // Method for checking whether an object should be considered a model for 1213 // the purposes of adding to the collection. 1214 // 判断 model 是否是一个模型对象 1215 _isModel: function (model) { 1216 return model instanceof Model; 1217 }, 1218 1219 // Internal method to create a model‘s ties to a collection. 1220 _addReference: function (model, options) { 1221 // this._byId 是一个对象,key 是模型的 cid,value 是模型对象 1222 this._byId[model.cid] = model; 1223 // 获取 model 的 id 1224 var id = this.modelId(model.attributes); 1225 // 根据 id 属性也确保能够找到 model 1226 if (id != null) this._byId[id] = model; 1227 // 给模型对象绑定事件 1228 model.on(‘all‘, this._onModelEvent, this); 1229 }, 1230 1231 // Internal method to sever a model‘s ties to a collection. 1232 _removeReference: function (model, options) { 1233 // 删除 model.collection,切断 model 与 collection 的联系 1234 if (this === model.collection) delete model.collection; 1235 // 解除事件绑定 1236 model.off(‘all‘, this._onModelEvent, this); 1237 }, 1238 1239 // Internal method called every time a model in the set fires an event. 1240 // Sets need to update their indexes when models change ids. All other 1241 // events simply proxy through. "add" and "remove" events that originate 1242 // in other collections are ignored. 1243 _onModelEvent: function (event, model, collection, options) { 1244 if ((event === ‘add‘ || event === ‘remove‘) && collection !== this) return; 1245 if (event === ‘destroy‘) this.remove(model, options); 1246 if (event === ‘change‘) { 1247 var prevId = this.modelId(model.previousAttributes()); 1248 var id = this.modelId(model.attributes); 1249 if (prevId !== id) { 1250 if (prevId != null) delete this._byId[prevId]; 1251 if (id != null) this._byId[id] = model; 1252 } 1253 } 1254 // 每个在 model 触发的 change 事件,model 所属的 collection 上也需要触发 1255 this.trigger.apply(this, arguments); 1256 } 1257 1258 }); 1259 1260 // Underscore methods that we want to implement on the Collection. 1261 // 90% of the core usefulness of Backbone Collections is actually implemented 1262 // right here: 1263 var methods = [‘forEach‘, ‘each‘, ‘map‘, ‘collect‘, ‘reduce‘, ‘foldl‘, 1264 ‘inject‘, ‘reduceRight‘, ‘foldr‘, ‘find‘, ‘detect‘, ‘filter‘, ‘select‘, 1265 ‘reject‘, ‘every‘, ‘all‘, ‘some‘, ‘any‘, ‘include‘, ‘contains‘, ‘invoke‘, 1266 ‘max‘, ‘min‘, ‘toArray‘, ‘size‘, ‘first‘, ‘head‘, ‘take‘, ‘initial‘, ‘rest‘, 1267 ‘tail‘, ‘drop‘, ‘last‘, ‘without‘, ‘difference‘, ‘indexOf‘, ‘shuffle‘, 1268 ‘lastIndexOf‘, ‘isEmpty‘, ‘chain‘, ‘sample‘, ‘partition‘]; 1269 1270 // Mix in each Underscore method as a proxy to `Collection#models`. 1271 _.each(methods, function (method) { 1272 if (!_[method]) return; 1273 Collection.prototype[method] = function () { 1274 var args = slice.call(arguments); 1275 // this.models 作为第一个参数 1276 args.unshift(this.models); 1277 return _[method].apply(_, args); 1278 }; 1279 }); 1280 1281 // Underscore methods that take a property name as an argument. 1282 var attributeMethods = [‘groupBy‘, ‘countBy‘, ‘sortBy‘, ‘indexBy‘]; 1283 1284 // Use attributes instead of properties. 1285 _.each(attributeMethods, function (method) { 1286 if (!_[method]) return; 1287 Collection.prototype[method] = function (value, context) { 1288 var iterator = _.isFunction(value) ? value : function (model) { 1289 return model.get(value); 1290 }; 1291 return _[method](this.models, iterator, context); 1292 }; 1293 }); 1294 1295 // Backbone.View 1296 // ------------- 1297 1298 // Backbone Views are almost more convention than they are actual code. A View 1299 // is simply a JavaScript object that represents a logical chunk of UI in the 1300 // DOM. This might be a single item, an entire list, a sidebar or panel, or 1301 // even the surrounding frame which wraps your whole app. Defining a chunk of 1302 // UI as a **View** allows you to define your DOM events declaratively, without 1303 // having to worry about render order ... and makes it easy for the view to 1304 // react to specific changes in the state of your models. 1305 1306 // Creating a Backbone.View creates its initial element outside of the DOM, 1307 // if an existing element is not provided... 1308 var View = Backbone.View = function (options) { 1309 this.cid = _.uniqueId(‘view‘); 1310 options || (options = {}); 1311 // this.viewOptions = options.viewOptions 1312 _.extend(this, _.pick(options, viewOptions)); 1313 // _ensureElement() 方法中会调用 setElement() 方法,setElement() 方法中调用 delegateEvents() 方法绑定 events 中的事件 1314 this._ensureElement(); 1315 this.initialize.apply(this, arguments); 1316 }; 1317 1318 // 代理事件的写法: ‘.red click‘,也可以设置成‘::‘分割: ‘.red::click‘ -> JPX就是这么做的 1319 // Cached regex to split keys for `delegate`. 1320 var delegateEventSplitter = /^(\S+)\s*(.*)$/; 1321 1322 // 视图选项 1323 // List of view options to be merged as properties. 1324 var viewOptions = [‘model‘, ‘collection‘, ‘el‘, ‘id‘, ‘attributes‘, ‘className‘, ‘tagName‘, ‘events‘]; 1325 1326 // Set up all inheritable **Backbone.View** properties and methods. 1327 _.extend(View.prototype, Events, { 1328 1329 // The default `tagName` of a View‘s element is `"div"`. 1330 tagName: ‘div‘, 1331 1332 // jQuery delegate for element lookup, scoped to DOM elements within the 1333 // current view. This should be preferred to global lookups where possible. 1334 // 使用 view.$(selector) 获取的是视图内的 dom 节点 1335 $: function (selector) { 1336 return this.$el.find(selector); 1337 }, 1338 1339 // Initialize is an empty function by default. Override it with your own 1340 // initialization logic. 1341 initialize: function () {}, 1342 1343 // **render** is the core function that your view should override, in order 1344 // to populate its element (`this.el`), with the appropriate HTML. The 1345 // convention is for **render** to always return `this`. 1346 render: function () { 1347 return this; 1348 }, 1349 1350 // Remove this view by taking the element out of the DOM, and removing any 1351 // applicable Backbone.Events listeners. 1352 remove: function () { 1353 // 卸载 el 节点 1354 this._removeElement(); 1355 this.stopListening(); 1356 return this; 1357 }, 1358 1359 // Remove this view‘s element from the document and all event listeners 1360 // attached to it. Exposed for subclasses using an alternative DOM 1361 // manipulation API. 1362 _removeElement: function () { 1363 this.$el.remove(); 1364 }, 1365 1366 // Change the view‘s element (`this.el` property) and re-delegate the 1367 // view‘s events on the new element. 1368 setElement: function (element) { 1369 // 先解绑事件 1370 this.undelegateEvents(); 1371 this._setElement(element); 1372 // 在绑定事件 1373 this.delegateEvents(); 1374 return this; 1375 }, 1376 1377 // Creates the `this.el` and `this.$el` references for this view using the 1378 // given `el`. `el` can be a CSS selector or an HTML string, a jQuery 1379 // context or an element. Subclasses can override this to utilize an 1380 // alternative DOM manipulation API and are only required to set the 1381 // `this.el` property. 1382 _setElement: function (el) { 1383 // 保存一个 jQuery 对象的 el 节点的引用 1384 this.$el = el instanceof Backbone.$ ? el : Backbone.$(el); 1385 this.el = this.$el[0]; 1386 }, 1387 1388 // Set callbacks, where `this.events` is a hash of 1389 // 1390 // *{"event selector": "callback"}* 1391 // 1392 // { 1393 // ‘mousedown .title‘: ‘edit‘, 1394 // ‘click .button‘: ‘save‘, 1395 // ‘click .open‘: function(e) { ... } 1396 // } 1397 // 1398 // pairs. Callbacks will be bound to the view, with `this` set properly. 1399 // Uses event delegation for efficiency. 1400 // Omitting the selector binds the event to `this.el`. 1401 delegateEvents: function (events) { 1402 // _result(this, ‘events‘) -> 如果 this.events 是一个函数,则执行这个函数并返回结果;如果 this.events 不是一个函数,直接返回 this.events 1403 if (!(events || (events = _.result(this, ‘events‘)))) return this; 1404 // 绑定之前 解绑所有事件,注意:Backbone 的所有事件都是批量一次性绑定完成的,不能一个一个的绑 1405 this.undelegateEvents(); 1406 for (var key in events) { 1407 var method = events[key]; 1408 // callback 也可以是 view 实例的一个方法名 1409 if (!_.isFunction(method)) method = this[events[key]]; 1410 if (!method) continue; 1411 // 分割 events 名, match[1] 为被代理元素,match[2] 为事件名 1412 var match = key.match(delegateEventSplitter); 1413 // 代理绑定 1414 this.delegate(match[1], match[2], _.bind(method, this)); 1415 } 1416 return this; 1417 }, 1418 1419 // Add a single event listener to the view‘s element (or a child element 1420 // using `selector`). This only works for delegate-able events: not `focus`, 1421 // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. 1422 delegate: function (eventName, selector, listener) { 1423 // 直接使用 jQuery 的事件绑定方法,并且事件命名在 .delegateEvents + cid 命名空间下 1424 this.$el.on(eventName + ‘.delegateEvents‘ + this.cid, selector, listener); 1425 }, 1426 1427 // Clears all callbacks previously bound to the view by `delegateEvents`. 1428 // You usually don‘t need to use this, but may wish to if you have multiple 1429 // Backbone views attached to the same DOM element. 1430 undelegateEvents: function () { 1431 // 删除 .delegateEvents + cid 命名空间下绑定的所有事件 1432 if (this.$el) this.$el.off(‘.delegateEvents‘ + this.cid); 1433 return this; 1434 }, 1435 1436 // A finer-grained `undelegateEvents` for removing a single delegated event. 1437 // `selector` and `listener` are both optional. 1438 undelegate: function (eventName, selector, listener) { 1439 // 删除 .delegateEvents + cid 命名空间下 指定事件名,指定的 被代理元素,指定的 事件处理函数绑定 1440 this.$el.off(eventName + ‘.delegateEvents‘ + this.cid, selector, listener); 1441 }, 1442 1443 // Produces a DOM element to be assigned to your view. Exposed for 1444 // subclasses using an alternative DOM manipulation API. 1445 _createElement: function (tagName) { 1446 return document.createElement(tagName); 1447 }, 1448 1449 // Ensure that the View has a DOM element to render into. 1450 // If `this.el` is a string, pass it through `$()`, take the first 1451 // matching element, and re-assign it to `el`. Otherwise, create 1452 // an element from the `id`, `className` and `tagName` properties. 1453 _ensureElement: function () { 1454 if (!this.el) { 1455 // 没有传递 el 属性,则 根据 id className tagName 等属性创建一个新的 dom 节点作为视图节点 1456 var attrs = _.extend({}, _.result(this, ‘attributes‘)); 1457 if (this.id) attrs.id = _.result(this, ‘id‘); 1458 if (this.className) attrs[‘class‘] = _.result(this, ‘className‘); 1459 // $el 和 el 保存创建的新节点的引用 1460 this.setElement(this._createElement(_.result(this, ‘tagName‘))); 1461 this._setAttributes(attrs); 1462 } else { 1463 // 保存 $el 和 el 的引用 1464 this.setElement(_.result(this, ‘el‘)); 1465 } 1466 }, 1467 1468 // Set attributes from a hash on this view‘s element. Exposed for 1469 // subclasses using an alternative DOM manipulation API. 1470 _setAttributes: function (attributes) { 1471 // attributes 属性,常用的有 id className tagName 等字段 1472 this.$el.attr(attributes); 1473 } 1474 1475 }); 1476 1477 // Backbone.sync 1478 // ------------- 1479 1480 // Override this function to change the manner in which Backbone persists 1481 // models to the server. You will be passed the type of request, and the 1482 // model in question. By default, makes a RESTful Ajax request 1483 // to the model‘s `url()`. Some possible customizations could be: 1484 // 1485 // * Use `setTimeout` to batch rapid-fire updates into a single request. 1486 // * Send up the models as XML instead of JSON. 1487 // * Persist models via WebSockets instead of Ajax. 1488 // 1489 // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests 1490 // as `POST`, with a `_method` parameter containing the true HTTP method, 1491 // as well as all requests with the body as `application/x-www-form-urlencoded` 1492 // instead of `application/json` with the model in a param named `model`. 1493 // Useful when interfacing with server-side languages like **PHP** that make 1494 // it difficult to read the body of `PUT` requests. 1495 Backbone.sync = function (method, model, options) { 1496 // 请求提交类型 1497 var type = methodMap[method]; 1498 1499 // Default options, unless specified. 1500 _.defaults(options || (options = {}), { 1501 emulateHTTP: Backbone.emulateHTTP, 1502 emulateJSON: Backbone.emulateJSON 1503 }); 1504 1505 // Default JSON-request options. 1506 var params = { 1507 type: type, 1508 dataType: ‘json‘ 1509 }; 1510 1511 // Ensure that we have a URL. 1512 if (!options.url) { 1513 params.url = _.result(model, ‘url‘) || urlError(); 1514 } 1515 1516 // Ensure that we have the appropriate request data. 1517 if (options.data =http://www.mamicode.com/= null && model && (method === ‘create‘ || method === ‘update‘ || method === ‘patch‘)) { 1518 params.contentType = ‘application/json‘; 1519 // 直接转化成 JSON 字符串 1520 params.data = http://www.mamicode.com/JSON.stringify(options.attrs || model.toJSON(options)); 1521 } 1522 1523 // For older servers, emulate JSON by encoding the request into an HTML-form. 1524 // 老的服务器,不支持 application/json 形式的 contentType,可以使用 表单数据 application/x-www-form-urlencoded 来模拟。 1525 // 表单数据提交有两种类型 encType: 1526 // 普通控件使用 application/x-www-form-urlencoded 类型提交 1527 // 带file控件的表单数据使用 multipart/form-data 类型提交 1528 if (options.emulateJSON) { 1529 params.contentType = ‘application/x-www-form-urlencoded‘; 1530 params.data = http://www.mamicode.com/params.data ? { 1531 model: params.data 1532 } : {}; 1533 } 1534 1535 // For older servers, emulate HTTP by mimicking the HTTP method with `_method` 1536 // And an `X-HTTP-Method-Override` header. 1537 // 老的服务器,不支持 PUT/DELETE/PATCH 类型的请求。则我们在发送的时候还是使用 POST 类型来发送请求,但是 data._method 属性中保存我们真实的 请求类型 1538 if (options.emulateHTTP && (type === ‘PUT‘ || type === ‘DELETE‘ || type === ‘PATCH‘)) { 1539 params.type = ‘POST‘; 1540 if (options.emulateJSON) params.data._method = type; 1541 var beforeSend = options.beforeSend; 1542 options.beforeSend = function (xhr) { 1543 xhr.setRequestHeader(‘X-HTTP-Method-Override‘, type); 1544 if (beforeSend) return beforeSend.apply(this, arguments); 1545 }; 1546 } 1547 1548 // Don‘t process data on a non-GET request. 1549 if (params.type !== ‘GET‘ && !options.emulateJSON) { 1550 params.processData = http://www.mamicode.com/false; 1551 } 1552 1553 // Pass along `textStatus` and `errorThrown` from jQuery. 1554 var error = options.error; 1555 options.error = function (xhr, textStatus, errorThrown) { 1556 options.textStatus = textStatus; 1557 options.errorThrown = errorThrown; 1558 if (error) error.apply(this, arguments); 1559 }; 1560 1561 // Make the request, allowing the user to override any Ajax options. 1562 var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); 1563 // 触发模型的 request 事件 1564 model.trigger(‘request‘, model, xhr, options); 1565 return xhr; 1566 }; 1567 1568 // Map from CRUD to HTTP for our default `Backbone.sync` implementation. 1569 var methodMap = { 1570 ‘create‘: ‘POST‘, 1571 ‘update‘: ‘PUT‘, 1572 ‘patch‘: ‘PATCH‘, 1573 ‘delete‘: ‘DELETE‘, 1574 ‘read‘: ‘GET‘ 1575 }; 1576 1577 // Set the default implementation of `Backbone.ajax` to proxy through to `$`. 1578 // Override this if you‘d like to use a different library. 1579 Backbone.ajax = function () { 1580 // 调用 jQuery 的 ajax() 方法 1581 return Backbone.$.ajax.apply(Backbone.$, arguments); 1582 }; 1583 1584 // Backbone.Router 1585 // --------------- 1586 1587 // Routers map faux-URLs to actions, and fire events when routes are 1588 // matched. Creating a new one sets its `routes` hash, if not set statically. 1589 var Router = Backbone.Router = function (options) { 1590 options || (options = {}); 1591 if (options.routes) this.routes = options.routes; 1592 // 根据 routes 对象,对其中的每一个映射使用 route 方法进行绑定 1593 this._bindRoutes(); 1594 this.initialize.apply(this, arguments); 1595 }; 1596 1597 // Cached regular expressions for matching named param parts and splatted 1598 // parts of route strings. 1599 var optionalParam = /\((.*?)\)/g; 1600 var namedParam = /(\(\?)?:\w+/g; 1601 var splatParam = /\*\w+/g; 1602 var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; 1603 1604 // Set up all inheritable **Backbone.Router** properties and methods. 1605 _.extend(Router.prototype, Events, { 1606 1607 // Initialize is an empty function by default. Override it with your own 1608 // initialization logic. 1609 initialize: function () {}, 1610 1611 // Manually bind a single named route to a callback. For example: 1612 // 1613 // this.route(‘search/:query/p:num‘, ‘search‘, function(query, num) { 1614 // ... 1615 // }); 1616 // 1617 route: function (route, name, callback) { 1618 // 如果 route 不是一个正则表达式而是Backbone的路径映射字符串,则需要转化成正则表达式 1619 if (!_.isRegExp(route)) route = this._routeToRegExp(route); 1620 // name 的用途是在绑定 route:name 事件时有用,不考虑绑定事件,则可以省略 name 参数 1621 // eg: this.route(‘(/)‘, function(){ alert(‘route‘) }) 1622 if (_.isFunction(name)) { 1623 callback = name; 1624 name = ‘‘; 1625 } 1626 // callback 没传,则默认 this.name 作为回调 1627 // eg: this.route(‘(/)‘, ‘index‘); 1628 if (!callback) callback = this[name]; 1629 var router = this; 1630 Backbone.history.route(route, function (fragment) { 1631 var args = router._extractParameters(route, fragment); 1632 // 在 callback 中返回false导致 1633 if (router.execute(callback, args, name) !== false) { 1634 // 触发 router 的 route:name 事件 1635 router.trigger.apply(router, [‘route:‘ + name].concat(args)); 1636 // 触发 router 的 route 事件 1637 router.trigger(‘route‘, name, args); 1638 // 触发 Backbone.history 的 route 事件 1639 Backbone.history.trigger(‘route‘, router, name, args); 1640 } 1641 }); 1642 return this; 1643 }, 1644 1645 // Execute a route handler with the provided parameters. This is an 1646 // excellent place to do pre-route setup or post-route cleanup. 1647 execute: function (callback, args, name) { 1648 if (callback) callback.apply(this, args); 1649 }, 1650 1651 // Simple proxy to `Backbone.history` to save a fragment into the history. 1652 navigate: function (fragment, options) { 1653 // 调用 history 对象的 navigate() 方法 1654 Backbone.history.navigate(fragment, options); 1655 return this; 1656 }, 1657 1658 // Bind all defined routes to `Backbone.history`. We have to reverse the 1659 // order of the routes here to support behavior where the most general 1660 // routes can be defined at the bottom of the route map. 1661 _bindRoutes: function () { 1662 if (!this.routes) return; 1663 // this.routes 通常是一个 路径 映射对象 1664 this.routes = _.result(this, ‘routes‘); 1665 var route, routes = _.keys(this.routes); 1666 while ((route = routes.pop()) != null) { 1667 // route 是映射串,toutes[route] 是对应的 Action 函数 1668 this.route(route, this.routes[route]); 1669 } 1670 }, 1671 1672 // Convert a route string into a regular expression, suitable for matching 1673 // against the current location hash. 1674 // 转化路由字符串成为一个正则表达式 1675 _routeToRegExp: function (route) { 1676 route = route.replace(escapeRegExp, ‘\\$&‘) 1677 .replace(optionalParam, ‘(?:$1)?‘) 1678 .replace(namedParam, function (match, optional) { 1679 return optional ? match : ‘([^/?]+)‘; 1680 }) 1681 .replace(splatParam, ‘([^?]*?)‘); 1682 return new RegExp(‘^‘ + route + ‘(?:\\?([\\s\\S]*))?$‘); 1683 }, 1684 1685 // Given a route, and a URL fragment that it matches, return the array of 1686 // extracted decoded parameters. Empty or unmatched parameters will be 1687 // treated as `null` to normalize cross-browser behavior. 1688 // 抽取参数,给定一个 正则表达式route 和 URL 片段fragment 1689 _extractParameters: function (route, fragment) { 1690 var params = route.exec(fragment).slice(1); 1691 return _.map(params, function (param, i) { 1692 // Don‘t decode the search params. 1693 if (i === params.length - 1) return param || null; 1694 return param ? decodeURIComponent(param) : null; 1695 }); 1696 } 1697 1698 }); 1699 1700 // Backbone.History 1701 // ---------------- 1702 1703 // Handles cross-browser history management, based on either 1704 // [pushState](http://diveintohtml5.info/history.html) and real URLs, or 1705 // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) 1706 // and URL fragments. If the browser supports neither (old IE, natch), 1707 // falls back to polling. 1708 var History = Backbone.History = function () { 1709 // 初始化 this.handlers 数组,这个数组用来保存所有的 router 实例 注册的 路径-回调关系 1710 this.handlers = []; 1711 // _.bindAll() 方法将 this.checkUrl 方法中的this固定死为this,相当于代理。这样可以确保在 this.checkUrl 作为事件处理函数时,this 依然能够指向 this.history 对象 1712 _.bindAll(this, ‘checkUrl‘); 1713 1714 // Ensure that `History` can be used outside of the browser. 1715 if (typeof window !== ‘undefined‘) { 1716 this.location = window.location; 1717 this.history = window.history; 1718 } 1719 }; 1720 1721 // Cached regex for stripping a leading hash/slash and trailing space. 1722 var routeStripper = /^[#\/]|\s+$/g; 1723 1724 // Cached regex for stripping leading and trailing slashes. 1725 var rootStripper = /^\/+|\/+$/g; 1726 1727 // Cached regex for stripping urls of hash. 1728 var pathStripper = /#.*$/; 1729 1730 // Has the history handling already been started? 1731 History.started = false; 1732 1733 // Set up all inheritable **Backbone.History** properties and methods. 1734 _.extend(History.prototype, Events, { 1735 1736 // The default interval to poll for hash changes, if necessary, is 1737 // twenty times a second. 1738 interval: 50, 1739 1740 // Are we at the app root? 1741 atRoot: function () { 1742 // 非 ‘/‘ 结尾的需要在末尾添加 ‘/‘ 1743 var path = this.location.pathname.replace(/[^\/]$/, ‘$&/‘); 1744 // pathname 为 this.root 并且 ?search 字符串不为空 1745 return path === this.root && !this.getSearch(); 1746 }, 1747 1748 // In IE6, the hash fragment and search params are incorrect if the 1749 // fragment contains `?`. 1750 getSearch: function () { 1751 // ? 字符串必须在 # 之前,如果在 # 之后会被全部替换掉,因此获取不到 1752 var match = this.location.href.replace(/#.*/, ‘‘).match(/\?.+/); 1753 return match ? match[0] : ‘‘; 1754 }, 1755 1756 // Gets the true hash value. Cannot use location.hash directly due to bug 1757 // in Firefox where location.hash will always be decoded. 1758 getHash: function (window) { 1759 var match = (window || this).location.href.match(/#(.*)$/); 1760 return match ? match[1] : ‘‘; 1761 }, 1762 1763 // Get the pathname and search params, without the root. 1764 getPath: function () { 1765 var path = decodeURI(this.location.pathname + this.getSearch()); 1766 var root = this.root.slice(0, -1); 1767 if (!path.indexOf(root)) path = path.slice(root.length); 1768 return path.charAt(0) === ‘/‘ ? path.slice(1) : path; 1769 }, 1770 1771 // Get the cross-browser normalized URL fragment from the path or hash. 1772 getFragment: function (fragment) { 1773 if (fragment == null) { 1774 if (this._hasPushState || !this._wantsHashChange) { 1775 // 如果 支持 pushState 并且没有设置 _wantsHashChange,则默认获取路径 1776 fragment = this.getPath(); 1777 } else { 1778 // 否则 获取 hash 值 1779 fragment = this.getHash(); 1780 } 1781 } 1782 // 如果传递了 fragment 参数,则仅仅是滤出开头的 # / 或者空格 1783 return fragment.replace(routeStripper, ‘‘); 1784 }, 1785 1786 // Start the hash change handling, returning `true` if the current URL matches 1787 // an existing route, and `false` otherwise. 1788 start: function (options) { 1789 // 只能启动一次 1790 if (History.started) throw new Error(‘Backbone.history has already been started‘); 1791 History.started = true; 1792 1793 // Figure out the initial configuration. Do we need an iframe? 1794 // Is pushState desired ... is it available? 1795 this.options = _.extend({ 1796 root: ‘/‘ // 当使用URL中的路径去匹配路由时,需要指定一个base/root url 1797 }, this.options, options); 1798 this.root = this.options.root; 1799 this._wantsHashChange = this.options.hashChange !== false; // 是否想要监控 hash 的变化,默认 true -> 为true时Backbone抓取URL中的hash值去匹配 1800 this._hasHashChange = ‘onhashchange‘ in window; // 是否支持 onhashchange 事件 1801 this._wantsPushState = !!this.options.pushState; // 是否想要监控 pushState 的变化,默认false -> 为true时Backbone抓取URL中的路径部分去匹配 1802 // 初始化时,this.history 指向了 window.history 1803 this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); // 是否支持pushState API 1804 this.fragment = this.getFragment(); // 初始化 this.fragment,用这个值去和 current fragment 去比较来判断页面状态是否发生变化 1805 1806 // Normalize root to always include a leading and trailing slash. 1807 // 在收尾加上‘/‘,并且过滤去 this.root 开头或者结尾的多余‘/‘,如 ‘///aa//‘ -> ‘/a/‘ 1808 this.root = (‘/‘ + this.root + ‘/‘).replace(rootStripper, ‘/‘); 1809 1810 // Transition from hashChange to pushState or vice versa if both are 1811 // requested. 1812 if (this._wantsHashChange && this._wantsPushState) { 1813 1814 // If we‘ve started off with a route from a `pushState`-enabled 1815 // browser, but we‘re currently in a browser that doesn‘t support it... 1816 // 浏览器不支持 pushState,但是现在有不在 根路径上:则需要将 当前的路径相对于root路径的相对路径转化为 hash值,然后重定向到 转化之后的新的 url 上 1817 if (!this._hasPushState && !this.atRoot()) { 1818 var root = this.root.slice(0, -1) || ‘/‘; 1819 this.location.replace(root + ‘#‘ + this.getPath()); 1820 // Return immediately as browser will do redirect to new url 1821 return true; 1822 1823 // Or if we‘ve started out with a hash-based route, but we‘re currently 1824 // in a browser where it could be `pushState`-based instead... 1825 // 浏览器支持 pushState 并且当前在 根路径上,需要把 hash 值转化到为相对于root路径的相对路径,导航到新的 url 中 1826 } else if (this._hasPushState && this.atRoot()) { 1827 this.navigate(this.getHash(), { 1828 replace: true 1829 }); 1830 } 1831 1832 } 1833 1834 // Proxy an iframe to handle location events if the browser doesn‘t 1835 // support the `hashchange` event, HTML5 history, or the user wants 1836 // `hashChange` but not `pushState`. 1837 // 当需要使用 hashChange,但是浏览器不支持 onhashchange 时,创建一个隐藏的 iframe 1838 if (!this._hasHashChange && this._wantsHashChange && (!this._wantsPushState || !this._hasPushState)) { 1839 var iframe = document.createElement(‘iframe‘); 1840 iframe.src = ‘javascript:0‘; 1841 iframe.style.display = ‘none‘; 1842 iframe.tabIndex = -1; 1843 var body = document.body; 1844 // Using `appendChild` will throw on IE < 9 if the document is not ready. 1845 this.iframe = body.insertBefore(iframe, body.firstChild).contentWindow; 1846 this.iframe.document.open().close(); 1847 this.iframe.location.hash = ‘#‘ + this.fragment; 1848 } 1849 1850 // Add a cross-platform `addEventListener` shim for older browsers. 1851 var addEventListener = window.addEventListener || function (eventName, listener) { 1852 return attachEvent(‘on‘ + eventName, listener); 1853 }; 1854 1855 // Depending on whether we‘re using pushState or hashes, and whether 1856 // ‘onhashchange‘ is supported, determine how we check the URL state. 1857 if (this._hasPushState) { 1858 // 支持 pushState API,监听 popstate 事件 1859 addEventListener(‘popstate‘, this.checkUrl, false); 1860 } else if (this._wantsHashChange && this._hasHashChange && !this.iframe) { 1861 // 支持 hashChange 时,监听 hashchange 事件 1862 addEventListener(‘hashchange‘, this.checkUrl, false); 1863 } else if (this._wantsHashChange) { 1864 // 否则,轮询检测 1865 this._checkUrlInterval = setInterval(this.checkUrl, this.interval); 1866 } 1867 // 启动时不想触发 route 事件时不执行,否则执行 loadUrl() 方法,loadUrl() 方法中去匹配路由并且执行对应的 action 1868 if (!this.options.silent) return this.loadUrl(); 1869 }, 1870 1871 // Disable Backbone.history, perhaps temporarily. Not useful in a real app, 1872 // but possibly useful for unit testing Routers. 1873 stop: function () { 1874 // Add a cross-platform `removeEventListener` shim for older browsers. 1875 var removeEventListener = window.removeEventListener || function (eventName, listener) { 1876 return detachEvent(‘on‘ + eventName, listener); 1877 }; 1878 1879 // Remove window listeners. 1880 // 解绑 popstate 事件或者 hanshchange 事件 1881 if (this._hasPushState) { 1882 removeEventListener(‘popstate‘, this.checkUrl, false); 1883 } else if (this._wantsHashChange && this._hasHashChange && !this.iframe) { 1884 removeEventListener(‘hashchange‘, this.checkUrl, false); 1885 } 1886 1887 // Clean up the iframe if necessary. 1888 // 删除创建的 空的 iframe 1889 if (this.iframe) { 1890 document.body.removeChild(this.iframe.frameElement); 1891 this.iframe = null; 1892 } 1893 // Some environments will throw when clearing an undefined interval. 1894 // 清除轮询定时器 1895 if (this._checkUrlInterval) clearInterval(this._checkUrlInterval); 1896 History.started = false; 1897 }, 1898 1899 // Add a route to be tested when the fragment changes. Routes added later 1900 // may override previous routes. 1901 route: function (route, callback) { 1902 // 将 路径匹配字符串和对应的回调 包装成一个对象,放进 this.handlers 数组 1903 this.handlers.unshift({ 1904 route: route, 1905 callback: callback 1906 }); 1907 }, 1908 1909 // Checks the current URL to see if it has changed, and if it has, 1910 // calls `loadUrl`, normalizing across the hidden iframe. 1911 // 检测当前 URL 是否发生变化,没有发生变化则返回false 1912 checkUrl: function (e) { 1913 // 获取当前的 URL 片段,可能是hash值,也可能是路径,视配置情况而定 1914 var current = this.getFragment(); 1915 1916 // If the user pressed the back button, the iframe‘s hash will have 1917 // changed and we should use that for comparison. 1918 if (current === this.fragment && this.iframe) { 1919 current = this.getHash(this.iframe); 1920 } 1921 // url 没有改变,则直接返回false 1922 if (current === this.fragment) return false; 1923 // 发生改变,若不支持 hashchange 事件使用 空的iframe 保持url时,本页面需要导航到 新的 url 1924 if (this.iframe) this.navigate(current); // 不保存记录,因为改变 iframe 的url时已经记录已经进入了history。也不触发事件,因为事件在 navigate 时已经触发了 1925 // 执行路由匹配 1926 this.loadUrl(); 1927 }, 1928 1929 // Attempt to load the current URL fragment. If a route succeeds with a 1930 // match, returns `true`. If no defined routes matches the fragment, 1931 // returns `false`. 1932 // 执行一次路由匹配,匹配不成功返回false,匹配成功返回true 1933 loadUrl: function (fragment) { 1934 fragment = this.fragment = this.getFragment(fragment); 1935 // _.any() 方法最多只能与一个 route 匹配 1936 return _.any(this.handlers, function (handler) { 1937 if (handler.route.test(fragment)) { 1938 handler.callback(fragment); 1939 return true; 1940 } 1941 }); 1942 }, 1943 1944 // Save a fragment into the hash history, or replace the URL state if the 1945 // ‘replace‘ option is passed. You are responsible for properly URL-encoding 1946 // the fragment in advance. 1947 // 1948 // The options object can contain `trigger: true` if you wish to have the 1949 // route callback be fired (not usually desirable), or `replace: true`, if 1950 // you wish to modify the current URL without adding an entry to the history. 1951 navigate: function (fragment, options) { 1952 if (!History.started) return false; 1953 // 默认不触发 route 事件,但是如果 options.trigger 为 true,则触发 route 事件 1954 if (!options || options === true) options = { 1955 trigger: !!options 1956 }; 1957 1958 // Normalize the fragment. 1959 fragment = this.getFragment(fragment || ‘‘); 1960 1961 // Don‘t include a trailing slash on the root. 1962 var root = this.root; 1963 // fragment为空串或者只是查询字符串(?),则root去除末尾的‘/‘ 1964 if (fragment === ‘‘ || fragment.charAt(0) === ‘?‘) { 1965 root = root.slice(0, -1) || ‘/‘; 1966 } 1967 var url = root + fragment; 1968 1969 // Strip the hash and decode for matching. 1970 // 去除 fragment 中的 #hash,并且加密 1971 fragment = decodeURI(fragment.replace(pathStripper, ‘‘)); 1972 1973 if (this.fragment === fragment) return; 1974 this.fragment = fragment; 1975 1976 // If pushState is available, we use it to set the fragment as a real URL. 1977 if (this._hasPushState) { 1978 // 使用 HTML5 history API 进行导航 1979 this.history[options.replace ? ‘replaceState‘ : ‘pushState‘]({}, document.title, url); 1980 1981 // If hash changes haven‘t been explicitly disabled, update the hash 1982 // fragment to store history. 1983 } else if (this._wantsHashChange) { 1984 this._updateHash(this.location, fragment, options.replace); 1985 if (this.iframe && (fragment !== this.getHash(this.iframe))) { 1986 // Opening and closing the iframe tricks IE7 and earlier to push a 1987 // history entry on hash-tag change. When replace is true, we don‘t 1988 // want this. 1989 // open().close() 用于欺骗 IE7 及以下版本保存历史记录 1990 if (!options.replace) this.iframe.document.open().close(); 1991 this._updateHash(this.iframe.location, fragment, options.replace); 1992 } 1993 1994 // If you‘ve told us that you explicitly don‘t want fallback hashchange- 1995 // based history, then `navigate` becomes a page refresh. 1996 // 如果明确指定不使用 hashchange,则在不支持pushState 的浏览器上使用刷新页面的方式 1997 } else { 1998 return this.location.assign(url); 1999 } 2000 // 触发事件 2001 if (options.trigger) return this.loadUrl(fragment); 2002 }, 2003 2004 // Update the hash location, either replacing the current entry, or adding 2005 // a new one to the browser history. 2006 _updateHash: function (location, fragment, replace) { 2007 if (replace) { 2008 var href = location.href.replace(/(javascript:|#).*$/, ‘‘); 2009 location.replace(href + ‘#‘ + fragment); 2010 } else { 2011 // Some browsers require that `hash` contains a leading #. 2012 location.hash = ‘#‘ + fragment; 2013 } 2014 } 2015 2016 }); 2017 2018 // Create the default Backbone.history. 2019 // Backbone.history 引用一个 History 实例,Backbone 的整个生命周期内,Backbone 只使用这个唯一个 History 实例 2020 Backbone.history = new History; 2021 2022 // Helpers 2023 // ------- 2024 2025 // Helper function to correctly set up the prototype chain, for subclasses. 2026 // Similar to `goog.inherits`, but uses a hash of prototype properties and 2027 // class properties to be extended. 2028 // 第一个参数是需要扩展的 实例属性,第二个参数是是需要扩展的 静态属性,并且返回一个子类 2029 var extend = function (protoProps, staticProps) { 2030 var parent = this; 2031 var child; 2032 2033 // The constructor function for the new subclass is either defined by you 2034 // (the "constructor" property in your `extend` definition), or defaulted 2035 // by us to simply call the parent‘s constructor. 2036 if (protoProps && _.has(protoProps, ‘constructor‘)) { 2037 // 如果 protoProps 中含有 constructor 属性,则子类调用 传递的 constructor 进行初始化 2038 child = protoProps.constructor; 2039 } else { 2040 // 否则,子类调用 父类的构造方法进行初始化 2041 child = function () { 2042 return parent.apply(this, arguments); 2043 }; 2044 } 2045 2046 // Add static properties to the constructor function, if supplied. 2047 // 扩展静态属性 2048 _.extend(child, parent, staticProps); 2049 2050 // Set the prototype chain to inherit from `parent`, without calling 2051 // `parent`‘s constructor function. 2052 // 如果直接使用 child.prototype = new parent() 会调用 父类 parent 的构造方法,因此需要一个 Surrogate 做中转 2053 var Surrogate = function () { 2054 this.constructor = child; 2055 }; 2056 Surrogate.prototype = parent.prototype; 2057 // child.prototype 继承 parent.prototype 的属性 2058 child.prototype = new Surrogate; 2059 2060 // Add prototype properties (instance properties) to the subclass, 2061 // if supplied. 2062 // 扩展实例属性 2063 if (protoProps) _.extend(child.prototype, protoProps); 2064 2065 // Set a convenience property in case the parent‘s prototype is needed 2066 // later. 2067 // 子类的 __super__ 属性引用父类的prototype 2068 child.__super__ = parent.prototype; 2069 // 返回子类 2070 return child; 2071 }; 2072 2073 // Set up inheritance for the model, collection, router, view and history. 2074 Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; 2075 2076 // Throw an error when a URL is needed, and none is supplied. 2077 var urlError = function () { 2078 throw new Error(‘A "url" property or function must be specified‘); 2079 }; 2080 2081 // Wrap an optional error callback with a fallback error event. 2082 var wrapError = function (model, options) { 2083 var error = options.error; 2084 options.error = function (resp) { 2085 if (error) error(model, resp, options); 2086 model.trigger(‘error‘, model, resp, options); 2087 }; 2088 }; 2089 2090 return Backbone; 2091 2092 }));
Backbone笔记(续)