
模式:解决某一类问题的通用方案 - 套路
MVC:一种架构模式,解耦代码,分离关注点
M(Model) - 数据模型 V(View) - 表现视图 C(Controller) - 控制器
传统web应用与 SPA 的区别:
状态概念代替了页面概念
每个类都可以实例化:
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();
extend( protoProp, staticProp ) 方法扩展子类:// extend() 方法第一个参数是需要扩展的实例方法,第二个参数是需要扩展的静态方法
var Man = d({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
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.Item(model.cid));break;case 'delete': veItem(model.cid); model = {};break;default: break;}console.log(method);
}
var model = new Backbone.Model({name:'旺财'});
model.save(); // -> create
// model.fetch(); -> read
Backbone.Events 是一个对象,拥有 on()/off()/trigger() 方法绑定、解绑、触发这个对象上的事件,也拥有 listenTo()/stopListening() 方法监听、解除监听别的对象上发生的事件。
Backbone 下的所有类的原型都通过拷贝继承的方式继承了这些方法,因此每个类的实例都能够使用这些方法获得事件的支持:
var model = new Backbone.Model({ age: 18 });
('xxx', function(){console.log( 'xxx is triggered!' );
});
igger('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 });
('change', function(){alert('age changed!');
});
model.set('age', 20, { silent: true }); // 不触发 change 事件
Backbone.Model 用来封装一个 JSON 对象,通过 attributes 属性引用 JSON 对象:
var Man = d({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();
('change:name', function(model,val){ console.log('val:' + val); }); model.set('age', '18'); model.set('name', 'PXM'); // val:PXM Backbone.Model 可以很方便的对 JSON 对象的属性值进行校验:
var MyModel = d({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 内部封装了一个数组,通过 models 引用这个数组,这个数组中保存着添加进该集合的所有数据模型实例:
var collection = new Backbone.Collection([{name: '旺财', age: 8}, {anme: '来福', age: 18}]);
console.dels); // [B…e.Model, B…e.Model]
与 Backbobe.Model 类似,应该避免使用 get()/add()/remove()/set() 等方法设置和获取数组的元素,使得 Backbone 能够监控数组的变化:
var collection = new Backbone.Collection();
('add', function(){console.log('集合中添加了新的模型...');
});
collection.add({name: '旺财', age: 8}); // 集合中添加了新的模型...
如果集合实例拥有 comparator 实例方法,则在进行 add()/remove()/set() 操作结束之后会自动调用 sort() 方法进行一次排序,并且触发一次 sort 事件:
var Girl = d();
var Girls = d({model: Girl,comparator: function(model1, model2) {var age1 = ('age');var age2 = ('age');if (age1 > age2) {return 1;} else if (age1 === age2) {return 0;} else {return -1;}}
});var girls = new Girls();('sort', function(){console.log('集合重新排序了...');
});girls.add([{ // 集合重新排序了...name: '小黄',age: 20
}, {name: '小红',age: 18
}, {name: '小兰',age: 22
}]);console.log(girls.pluck('name')); // ["小红", "小黄", "小兰"]
Backbone.Router 用来提供路径与 action 的映射关系。路径与路由字符串(或者正则表达式)去匹配,匹配上则调用对应的 action。
路由字符串中包含以下规则:
/之间的字符Backbone.Router 实例使用 route() 方法来注册路由字符串与action的映射关系:
var route1 = new Backbone.Router().route('(/)', 'index', function(){ // route 方法是另一种绑定路由 Action映射关系的方法console.log('index');
});
在 Backbone.Router 的构造函数中,会遍历 routes 属性,依次调用 route() 方法对 routes 对象中的每一个映射关系进行注册:
var MyRouter = d({routes: {"(/)": "index", // -> index()"help(/)": "help", // -> help()"search/:keyword(/p:page)(/)": "search", // -> search('baidu', 2)"download/*file(/)": "download", // .txt -> download('')"*error": "error" // -> 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 被调用时触发:
<('route:index', function(){alert('进入index了'); });
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.ute);
});
// /^help(?:?([sS]*))?$/
// /^index(?:?([sS]*))?$/
Backbone.history 监控页面状态变化的机制是:
当页面上所有的 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 绑定到一个 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 = d({initialize: function(){this.del, "change", der);},render: function(){this.$el.html('Hello,'('name'));}
});var view = new MyView({el: 'body',model: new Backbone.Model({name: 'PXM'})
});der();
// del.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 = d({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 = d({initialize: function(){der(); this.listenTo( del, 'change', der ); }, render: function(){ this.$el.html('Hello,<span>' + ('name') + "</span>"); } }); var view = new MyView({ el: '#view1', model: new Backbone.Model({'name': 'PXM'}) }); view.delegateEvents({"click span": function(){ var name = prompt('请输入新的名字:'); del.set('name', name || ''); }}); Backbone.View 在实例化时,会对 events 属性内部的事件自动调用 delegateEvents() 进行代理事件绑定:
var MyView = d({initialize: function() {der(); this.del, 'change', der); }, render: function() { this.$el.html('Hello,<span>' + ('name') + "</span>"); }, events: { 'click span': function() { var name = prompt('请输入新的名字:'); del.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 //
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 || der || 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 Conflict = 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 ulateHTTP = 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 ulateJSON = 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 // ('expand', function(){ alert('expanded'); });
78 // igger('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 (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 !== t
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 (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 (st(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 ({
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 // ( name, callback, context )
288 obj[action].apply(obj, [key, name[key]].concat(rest));
289 }
290 return false;
291 }
292
293 /* 空格分割形式:
294 ( 'change:name change:age', function(){
295 console.log( 'change:name or change:age fired' );
296 } );
297 */
298 // Handle space separated event names.
299 if (st(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.);
323 return;
324 case 1:
325 while (++i < l)(ev = events[i]).callback., a1);
326 return;
327 case 2:
328 while (++i < l)(ev = events[i]).callback., a1, a2);
329 return;
330 case 3:
331 while (++i < l)(ev = events[i]).callback., a1, a2, a3);
332 return;
333 default:
334 while (++i < l)(ev = events[i]).callback., args);
335 return;
336 }
337 };
338
339 // Aliases for backwards compatibility.
340 Events.bind = ;
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 // llecton 的作用???
363 if (llection) llection = llection;
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 _.(attr));
422 },
423
424 // Returns `true` if the attribute contains a value that is not null
425 // or undefined.
426 has: function (attr) {
427 (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 igger('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 igger('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 igger('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 igger('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 igger('destroy', model, llection, 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()) igger('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 _.llection, 'url') ||
707 urlError();
708 if (this.isNew()) return base;
709 place(/([^/])$/, '$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 structor(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 igger('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 (del) del = del;
797 if (optionsparator !== void 0) thisparator = optionsparator;
798 this._reset();
799 this.initialize.apply(this, arguments);
800 if (models) set(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 JSON(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 获取 dels 中的 model
863 var model = models[i] = (models[i]);
864 // 不存在 model则不进行任何操作
865 if (!model) continue;
866 // 删除 _byId 索引中的记录
867 var id = delId(model.attributes);
868 if (id != null) delete this._byId[id];
869 delete this._byId[model.cid];
870 // 删除 dels 中的 model
871 var index = this.indexOf(model);
872 dels.splice(index, 1);
873 this.length--;
874 // 触发 model 的 remove 事件
875 if (!options.silent) {
876 options.index = index;
877 igger('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 = thisparator && (at == null) && options.sort !== false;
901 // this,comparator 可以是字符串
902 var sortAttr = _.isString(thisparator) ? thisparator : null;
903 var toAdd = [],
904 toRemove = [],
905 modelMap = {};
906 var add = options.add,
907 merge = ,
908 remove = ve;
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 = (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 // -> 在这里llection 指向 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 = delId(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 || !dels[i] || model.cid !== dels[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 = dels[i]).cid]) toRemove.push(model);
962 }
963 if (toRemove.length) ve(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 数组插入到 dels 数组的指定位置处
973 for (var i = 0, length = toAdd.length; i < length; i++) {
974 dels.splice(at + i, 0, toAdd[i]);
975 }
976 } else {
977 // 否则,将 toAdd 插入到 dels 数组的后面
978 // order - 清空 dels
979 if (order) dels.length = 0;
980 var orderedModels = order || toAdd;
981 for (var i = 0, length = orderedModels.length; i < length; i++) {
982 dels.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) igger('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 = dels.length; i < length; i++) {
1018 // 删除每个 llection,解除每个 model 上的事件绑定
1019 this._dels[i], options);
1020 }
1021 // options.previousModels 保存reset之前的model集合
1022 options.previousModels = dels;
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) igger('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 ve(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 ve(model, options);
1059 return model;
1060 },
1061
1062 // Slice out a sub-array of models from the collection.
1063 slice: function () {
1064 return slice.dels, 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 = delId(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 dels[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 // 对 dels 中的数组进行一次排序,触发 sort 事件
1104 sort: function (options) {
1105 if (!thisparator) 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(thisparator) || thisparator.length === 1) {
1110 // 如果是字符串,则调用 _.sortBy() 方法按照指定的属性进行排序
1111 dels = this.sortBy(thisparator, this);
1112 } else {
1113 // 如果 thisparator 是函数,直接调数组的原生 sort() 方法进行排序
1114 dels.sort(_.bind(thisparator, this));
1115 }
1116 // 触发 sort 事件
1117 if (!options.silent) igger('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 _.dels, '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 = set ? 'reset' : 'set';
1137 collection[method](resp, options);
1138 if (success) success(collection, resp, options);
1139 igger('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 dels, {
1172 model: del,
1173 comparator: thisparator
1174 });
1175 },
1176
1177 // Define how to uniquely identify models in the collection.
1178 // 获取 模型属性对象 中的id
1179 modelId: function (attrs) {
1180 return del.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 dels = [];
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 本来就是一个模型对象,则将 llection 指向本对象,直接返回
1197 if (!llection) llection = this;
1198 return attrs;
1199 }
1200 options = options ? _.clone(options) : {};
1201 llection = this;
1202 // 调用 del 构造函数创建一个模型,并且 llection 指向本 collection
1203 var model = del(attrs, options);
1204 // 如果 options 中设置了 validate: true,则 del -> set() -> _validate(),验证错误信息保存在 model.validationError 中
1205 // 检验成功,则直接返回新创建的模型对象
1206 if (!model.validationError) return model;
1207 // 校验失败,则触发 collection 的 invalid 事件,并返回 false
1208 igger('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 = delId(model.attributes);
1225 // 根据 id 属性也确保能够找到 model
1226 if (id != null) this._byId[id] = model;
1227 // 给模型对象绑定事件
1228 ('all', this._onModelEvent, this);
1229 },
1230
1231 // Internal method to sever a model's ties to a collection.
1232 _removeReference: function (model, options) {
1233 // 删除 llection,切断 model 与 collection 的联系
1234 if (this === llection) llection;
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') ve(model, options);
1246 if (event === 'change') {
1247 var prevId = delId(model.previousAttributes());
1248 var id = delId(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 igger.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 // dels 作为第一个参数
1276 args.dels);
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 (value);
1290 };
1291 return _[method](dels, 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
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 ateElement(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 ulateHTTP` 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: ulateHTTP,
1502 emulateJSON: ulateJSON
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 == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
1518 tType = 'application/json';
1519 // 直接转化成 JSON 字符串
1520 params.data = JSON.stringify(options.attrs || JSON(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 (ulateJSON) {
1529 tType = 'application/x-www-form-urlencoded';
1530 params.data = 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 (ulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
1539 pe = 'POST';
1540 if (ulateJSON) 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 (pe !== 'GET' && !ulateJSON) {
1550 params.processData = false;
1551 }
1552
1553 // Pass along `textStatus` and `errorThrown` from jQuery.
1554 var error = ;
1555 = function (xhr, textStatus, errorThrown) {
1556 Status = textStatus;
1557 Thrown = 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 igger('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 (utes) utes = utes;
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 // ute('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: ute('(/)', function(){ alert('route') })
1622 if (_.isFunction(name)) {
1623 callback = name;
1624 name = '';
1625 }
1626 // callback 没传,则默认 this.name 作为回调
1627 // eg: ute('(/)', 'index');
1628 if (!callback) callback = this[name];
1629 var router = this;
1630 ute(route, function (fragment) {
1631 var args = router._extractParameters(route, fragment);
1632 // 在 callback 中返回false导致
1633 if (ute(callback, args, name) !== false) {
1634 // 触发 router 的 route:name 事件
1635 igger.apply(router, ['route:' + name].concat(args));
1636 // 触发 router 的 route 事件
1637 igger('route', name, args);
1638 // 触发 Backbone.history 的 route 事件
1639 igger('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 (!utes) return;
1663 // utes 通常是一个 路径 映射对象
1664 utes = _.result(this, 'routes');
1665 var route, routes = _.utes);
1666 while ((route = routes.pop()) != null) {
1667 // route 是映射串,toutes[route] 是对应的 Action 函数
1668 ute(route, utes[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 = place(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 = (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](.html) and real URLs, or
1705 // [onhashchange](.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.place(/[^/]$/, '$&/');
1744 // pathname 为 并且 ?search 字符串不为空
1745 return path === && !Search();
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.place(/#.*/, '').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 + Search());
1766 var 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 = Path();
1777 } else {
1778 // 否则 获取 hash 值
1779 fragment = Hash();
1780 }
1781 }
1782 // 如果传递了 fragment 参数,则仅仅是滤出开头的 # / 或者空格
1783 place(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 = ;
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 = Fragment(); // 初始化 this.fragment,用这个值去和 current fragment 去比较来判断页面状态是否发生变化
1805
1806 // Normalize root to always include a leading and trailing slash.
1807 // 在收尾加上'/',并且过滤去 开头或者结尾的多余'/',如 '///aa//' -> '/a/'
1808 = ('/' + + '/').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
1816 // 浏览器不支持 pushState,但是现在有不在 根路径上:则需要将 当前的路径相对于root路径的相对路径转化为 hash值,然后重定向到 转化之后的新的 url 上
1817 if (!this._hasPushState && !this.atRoot()) {
1818 var root = slice(0, -1) || '/';
1819 place(root + '#' + Path());
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`-
1825 // 浏览器支持 pushState 并且当前在 根路径上,需要把 hash 值转化到为相对于root路径的相对路径,导航到新的 url 中
1826 } else if (this._hasPushState && this.atRoot()) {
1827 this.Hash(), {
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 = ateElement('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 = veEventListener || 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 veChild(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 = Fragment();
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 = Hash(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 = Fragment(fragment);
1935 // _.any() 方法最多只能与一个 route 匹配
1936 return _.any(this.handlers, function (handler) {
1937 if (st(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 事件,但是如果 igger 为 true,则触发 route 事件
1954 if (!options || options === true) options = {
1955 trigger: !!options
1956 };
1957
1958 // Normalize the fragment.
1959 fragment = Fragment(fragment || '');
1960
1961 // Don't include a trailing slash on the root.
1962 var 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 = place(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.place ? '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, place);
1985 if (this.iframe && (fragment !== Hash(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 (!place) this.iframe.document.open().close();
1991 this._updateHash(this.iframe.location, fragment, place);
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 (igger) 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 = place(/(javascript:|#).*$/, '');
2009 place(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 = structor;
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 structor = 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 d = d = d = d = d = 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 = ;
2084 = function (resp) {
2085 if (error) error(model, resp, options);
2086 igger('error', model, resp, options);
2087 };
2088 };
2089
2090 return Backbone;
2091
2092 })); posted on 2016-12-16 21:10 哎呀吗呀 阅读( ...) 评论( ...) 编辑 收藏
转载于:.html
本文发布于:2024-02-04 06:08:43,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170700690552959.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
| 留言与评论(共有 0 条评论) |