首页 > 代码库 > 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笔记(续)