BackboneJS 源碼註釋

Backbone 做者在源碼中作了很好的註釋,這裏只是錦上添花,補充一些我的的理解而已。javascript

//     Backbone.js 1.2.3

//     (c) 2010-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
//     Backbone may be freely distributed under the MIT license.
//     For all details and documentation:
//     http://backbonejs.org

(function(factory) {

  // Establish the root object, `window` (`self`) in the browser, or `global` on the server.
  // We use `self` instead of `window` for `WebWorker` support.
  var root = (typeof self == 'object' && self.self == self && self) ||
            (typeof global == 'object' && global.global == global && global);

  // Set up Backbone appropriately for the environment. Start with AMD.
  if (typeof define === 'function' && define.amd) {
    define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
      // Export global even in AMD case in case this script is loaded with
      // others that may still expect a global Backbone.
      root.Backbone = factory(root, exports, _, $);
    });

  // Next for Node.js or CommonJS. jQuery may not be needed as a module.
  } else if (typeof exports !== 'undefined') {
    var _ = require('underscore'), $;
    try { $ = require('jquery'); } catch(e) {}
    factory(root, exports, _, $);

  // Finally, as a browser global.
  } else {
    root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
  }

}(function(root, Backbone, _, $) {

  // Initial Setup
  // -------------

  // Save the previous value of the `Backbone` variable, so that it can be
  // restored later on, if `noConflict` is used.
  var previousBackbone = root.Backbone;

  // Create a local reference to a common array method we'll want to use later.
  var slice = Array.prototype.slice;

  // Current version of the library. Keep in sync with `package.json`.
  Backbone.VERSION = '1.2.3';

  // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
  // the `$` variable.
  Backbone.$ = $;

  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
  // to its previous owner. Returns a reference to this Backbone object.
  Backbone.noConflict = function() {
    root.Backbone = previousBackbone;
    return this;
  };

  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
  // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
  // set a `X-Http-Method-Override` header.
  Backbone.emulateHTTP = false;

  // Turn on `emulateJSON` to support legacy servers that can't deal with direct
  // `application/json` requests ... this will encode the body as
  // `application/x-www-form-urlencoded` instead and will send the model in a
  // form param named `model`.
  Backbone.emulateJSON = false;

  // Proxy Backbone class methods to Underscore functions, wrapping the model's
  // `attributes` object or collection's `models` array behind the scenes.
  //
  // collection.filter(function(model) { return model.get('age') > 10 });
  // collection.each(this.addView);
  //
  // `Function#apply` can be slow so we use the method's arg count, if we know it.
  var addMethod = function(length, method, attribute) {
    switch (length) {
      case 1: return function() {
        return _[method](this[attribute]);
      };
      case 2: return function(value) {
        return _[method](this[attribute], value);
      };
      case 3: return function(iteratee, context) {
        return _[method](this[attribute], cb(iteratee, this), context);
      };
      case 4: return function(iteratee, defaultVal, context) {
        return _[method](this[attribute], cb(iteratee, this), defaultVal, context);
      };
      default: return function() {
        var args = slice.call(arguments);
        args.unshift(this[attribute]);
        return _[method].apply(_, args);
      };
    }
  };
  // 添加 underscore 方法
  var addUnderscoreMethods = function(Class, methods, attribute) {
    _.each(methods, function(length, method) {
      if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
    });
  };

  // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
  var cb = function(iteratee, instance) {
    if (_.isFunction(iteratee)) return iteratee;
    if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
    if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
    return iteratee;
  };
  var modelMatcher = function(attrs) {
    var matcher = _.matches(attrs);
    return function(model) {
      return matcher(model.attributes);
    };
  };

  // Backbone.Events - 事件
  // -------------------------

  // A module that can be mixed in to *any object* in order to provide it with
  // a custom event channel. You may bind a callback to an event with `on` or
  // remove with `off`; `trigger`-ing an event fires all callbacks in
  // succession.
  //
  //     var object = {};
  //     _.extend(object, Backbone.Events);
  //     object.on('expand', function(){ alert('expanded'); });
  //     object.trigger('expand');
  //
  // Events 全部事件行爲都保存在 obj._events 屬性中,
  // 不管是 on 仍是 listenTo,事件行爲都是保存在事件觸發者身上,
  // listenTo 其實只是 on 的另外一種調用形式。
  // 因此,當 events.off() 方法會解綁全部事件,同時也會解除全部被監聽關係。(即監聽者沒法再繼續監聽)
  // events.stopListening() 中止監聽全部事件,從被監聽者身上解除事件處理。
  // 
  // obj._listeningTo 是保存對被監聽對象的引用。
  // obj._listenId 是每一個事件觸發者本身身份的 ID,當本身被其餘人監聽時,用以標識本身身份。
  // obj._listeners 是全部對本身進行監聽的對象引用映射。
  // 
  // Backbone.Events 的 listenTo 與 stopListening 方法實現原理:
  // 例如監聽者 listener 和被監聽者 listenee。
  // 
  // `listener.listenTo(listenee, 'any', callback);`
  // 
  // 整個監聽過程大體能夠分爲三個部分:
  // 1. 生成監聽關係表:
  // 
  //  ```
  //  {
  //    count: Int,   // 監聽次數,當 count 爲 0 時,表示兩者再也不存在任何監聽關係,從雙方刪除監聽關係。
  //    id: String,   // listener._listenId,標識監聽者身份。
  //    listeningTo: Object,  // listener._listeningTo,保存全部監聽關係表(以被監聽者 ID 做爲主鍵)
  //    obj: listenee,  // listenee,被監聽者。
  //    objId: String   // listenee._listenId,被監聽者 ID。
  //  }
  //  ```
  //  
  // 2. 在 listenee 中保存事件以及事件回調,即在 listenee._events['any'] 隊列中推入事件處理關係表。
  // 
  // ```
  // {
  //  callback: Function,  // 回調函數
  //  context: Object,
  //  ctx: Object, 
  //  listening: Object   // listening 就是監聽關係表
  // }
  // ```
  //  
  //  3. 在雙方各自添加監聽關係:
  //    在 listener 方面,以 listenee._listenId 爲主鍵,保存在 listener._listeningTo 字段。
  //    在 listenee 方面,以 listener._listenId 爲主鍵,保存在 listenee._listeners 字段。
  //    即如下等式是成立的 `listener._listeningTo[listenee._listenId] === listenee._listeners[listener._listenId]`。
  // 
  // `listener.stopListening(listenee, 'any', callback);`
  // 
  // 中止監聽過程實際上是經過 `listenee.off('any', callback, listener)` 來實現,這一過程包括:
  //    從 listenee._events 移除事件處理關係表。
  //    經過監聽關係表裏的 count 字段減一併判斷是否爲 0,從而決定是否要從雙方移除監聽關係。
  var Events = Backbone.Events = {};

  // Regular expression used to split event strings.
  // 正則表達式:多個事件名以空格分隔。
  var eventSplitter = /\s+/;

  // Iterates over the standard `event, callback` (as well as the fancy multiple
  // space-separated events `"change blur", callback` and jQuery-style event
  // maps `{event: callback}`).
  // 
  // 遍歷定義的事件。
  // 
  // iteratee 是迭代函數,即 onApi, offApi, triggerApi, onceMap 函數
  // eventsApi 做用是將 events, name, callback, opts 參數整理成標準格式傳遞給 iteratee 調用。
  var eventsApi = function(iteratee, events, name, callback, opts) {
    var i = 0, names;
    if (name && typeof name === 'object') {
      // Handle event maps.
      if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
      for (names = _.keys(name); i < names.length ; i++) {
        events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
      }
    } else if (name && eventSplitter.test(name)) {
      // Handle space separated event names by delegating them individually.
      for (names = name.split(eventSplitter); i < names.length; i++) {
        events = iteratee(events, names[i], callback, opts);
      }
    } else {
      // Finally, standard events.
      //    events 事件映射表;
      //    name 事件名稱;
      //    callback 事件處理函數;
      //    opts 額外參數
      events = iteratee(events, name, callback, opts);
    }
    return events;
  };

  // Bind an event to a `callback` function. Passing `"all"` will bind
  // the callback to all events fired.
  // 綁定事件
  Events.on = function(name, callback, context) {
    return internalOn(this, name, callback, context);
  };

  // Guard the `listening` argument from the public API.
  // 內部綁定事件函數,它是 Events.on, Events.listenTo 公開 API 內部真正實現事件綁定的函數
  // 所以它比 Events.on, Events.listenTo 多一個參數 listening,
  // 該參數爲真,表示它正實現 listenTo 方法;不然表示它正式實現 on 方法。
  var internalOn = function(obj, name, callback, context, listening) {
    // 執行 eventsApi 
    obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
        context: context,
        ctx: obj,
        listening: listening
    });

    // 若是當前是實現 listenTo 方法,須要在被監聽者的 _listeners 中,添加監聽者的引用關係。
    if (listening) {
      var listeners = obj._listeners || (obj._listeners = {});
      // listening.id 是監聽者的 _listenId。
      listeners[listening.id] = listening;
    }

    return obj;
  };

  // Inversion-of-control versions of `on`. Tell *this* object to listen to
  // an event in another object... keeping track of what it's listening to
  // for easier unbinding later.
  // Events.on 操做的逆操做,表示監聽另外一個對象的事件,並保持對該對象的引用,以便解綁事件。
  Events.listenTo =  function(obj, name, callback) {
    // 若是 obj 爲否,則終止 listenTo 操做。
    if (!obj) return this;
    // 被監聽對象應該有一個惟一的監聽 ID,即 _listenId,用以標識被監聽者身份。
    var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
    // _listeningTo 是監聽行爲映射表,該表用以保存全部被監聽者的引用。
    var listeningTo = this._listeningTo || (this._listeningTo = {});
    var listening = listeningTo[id];

    // This object is not listening to any other events on `obj` yet.
    // Setup the necessary references to track the listening callbacks.
    // 若是被監聽者是首次被當前監聽者監聽,應初始化監聽引用。
    if (!listening) {
      // 監聽者的監聽 ID
      var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
      // 監聽引用保存的字段:
      //    obj: 被監聽對象。
      //    objId: 被監聽對象的監聽 ID。
      //    id: 監聽者監聽 ID。
      //    listeningTo: 監聽映射表。
      //    count: 監聽者對被監聽者監聽的次數
      listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
    }

    // Bind callbacks on obj, and keep track of them on listening.
    internalOn(obj, name, callback, this, listening);
    return this;
  };

  // The reducing API that adds a callback to the `events` object.
  // 綁定事件
  var onApi = function(events, name, callback, options) {
    // 只有給定事件處理函數才進行事件綁定
    if (callback) {
      // handlers 是事件處理函數組成的數組
      var handlers = events[name] || (events[name] = []);
      // context 事件處理函數上下文(用戶給出),ctx 事件觸發者(默認上下文),listening 監聽引用關係表
      var context = options.context, ctx = options.ctx, listening = options.listening;
      // 監聽計數加一。
      if (listening) listening.count++;
      // 事件處理函數集合增長一個事件處理
      handlers.push({ callback: callback, context: context, ctx: context || ctx, listening: listening });
    }
    return events;
  };

  // Remove one or many callbacks. If `context` is null, removes all
  // callbacks with that function. If `callback` is null, removes all
  // callbacks for the event. If `name` is null, removes all bound
  // callbacks for all events.
  // 解綁事件
  Events.off =  function(name, callback, context) {
    if (!this._events) return this;
    this._events = eventsApi(offApi, this._events, name, callback, {
        context: context,
        // 全部監聽者引用表
        listeners: this._listeners
    });
    return this;
  };

  // Tell this object to stop listening to either specific events ... or
  // to every object it's currently listening to.
  // 中止監聽
  Events.stopListening =  function(obj, name, callback) {
    // 被監聽者引用表
    var listeningTo = this._listeningTo;
    if (!listeningTo) return this;

    // 須要被中止監聽者 ID 集合(沒有指定被監聽者,則默認全部被監聽者)
    var ids = obj ? [obj._listenId] : _.keys(listeningTo);

    // 遍歷被中止監聽者 ID,逐個解除監聽。
    for (var i = 0; i < ids.length; i++) {
      var listening = listeningTo[ids[i]];

      // If listening doesn't exist, this object is not currently
      // listening to obj. Break out early.
      // 若是 listening 不存在,表示當前沒有監聽行爲。
      if (!listening) break;
      // 被監聽者從自身解除事件行爲。
      listening.obj.off(name, callback, this);
    }
    // 當沒有監放任何對象時,將 _listeningTo 屬性置爲 void 0。
    if (_.isEmpty(listeningTo)) this._listeningTo = void 0;

    return this;
  };

  // The reducing API that removes a callback from the `events` object.
  var offApi = function(events, name, callback, options) {
    // events 不存在,終止 off 操做
    if (!events) return;

    var i = 0, listening;
    // context 指定上下文,listeners 監聽者
    var context = options.context, listeners = options.listeners;

    // Delete all events listeners and "drop" events.
    // 沒有給定任何事件名、事件回調或上下文,則移除全部監聽者,以及事件。
    if (!name && !callback && !context) {
      // 生成全部監聽者 id。
      var ids = _.keys(listeners);
      // 遍歷全部監聽者 id,逐一接觸引用關係
      for (; i < ids.length; i++) {
        listening = listeners[ids[i]];
        delete listeners[listening.id];  // 移除監聽者引用
        delete listening.listeningTo[listening.objId];  // 移除監聽關係
      }
      return;
    }

    // 若是沒有指定事件名稱,則移除所有事件
    var names = name ? [name] : _.keys(events);
    for (; i < names.length; i++) {
      name = names[i];
      var handlers = events[name];

      // Bail out if there are no events stored.
      // 若是沒有回調函數,終止本次循環
      if (!handlers) break;

      // Replace events if there are any remaining.  Otherwise, clean up.
      var remaining = [];
      for (var j = 0; j < handlers.length; j++) {
        var handler = handlers[j];
        if (
          callback && callback !== handler.callback &&
            callback !== handler.callback._callback ||
              context && context !== handler.context
        ) {
          remaining.push(handler);
        } else {
          listening = handler.listening;
          if (listening && --listening.count === 0) {
            delete listeners[listening.id];
            delete listening.listeningTo[listening.objId];
          }
        }
      }

      // Update tail event if the list has any events.  Otherwise, clean up.
      if (remaining.length) {
        events[name] = remaining;
      } else {
        delete events[name];
      }
    }
    if (_.size(events)) return events;
  };

  // Bind an event to only be triggered a single time. After the first time
  // the callback is invoked, its listener will be removed. If multiple events
  // are passed in using the space-separated syntax, the handler will fire
  // once for each event, not once for a combination of all events.
  Events.once =  function(name, callback, context) {
    // Map the event into a `{event: once}` object.
    var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
    return this.on(events, void 0, context);
  };

  // Inversion-of-control versions of `once`.
  Events.listenToOnce =  function(obj, name, callback) {
    // Map the event into a `{event: once}` object.
    var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
    return this.listenTo(obj, events);
  };

  // Reduces the event callbacks into a map of `{event: onceWrapper}`.
  // `offer` unbinds the `onceWrapper` after it has been called.
  var onceMap = function(map, name, callback, offer) {
    if (callback) {
      var once = map[name] = _.once(function() {
        offer(name, once);
        callback.apply(this, arguments);
      });
      once._callback = callback;
    }
    return map;
  };

  // Trigger one or many events, firing all bound callbacks. Callbacks are
  // passed the same arguments as `trigger` is, apart from the event name
  // (unless you're listening on `"all"`, which will cause your callback to
  // receive the true name of the event as the first argument).
  Events.trigger =  function(name) {
    if (!this._events) return this;

    var length = Math.max(0, arguments.length - 1);
    var args = Array(length);
    for (var i = 0; i < length; i++) args[i] = arguments[i + 1];

    eventsApi(triggerApi, this._events, name, void 0, args);
    return this;
  };

  // Handles triggering the appropriate event callbacks.
  var triggerApi = function(objEvents, name, cb, args) {
    if (objEvents) {
      var events = objEvents[name];
      var allEvents = objEvents.all;
      if (events && allEvents) allEvents = allEvents.slice();
      if (events) triggerEvents(events, args);
      if (allEvents) triggerEvents(allEvents, [name].concat(args));
    }
    return objEvents;
  };

  // A difficult-to-believe, but optimized internal dispatch function for
  // triggering events. Tries to keep the usual cases speedy (most internal
  // Backbone events have 3 arguments).
  var triggerEvents = function(events, args) {
    var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
    switch (args.length) {
      case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
      case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
      case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
      case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
      default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
    }
  };

  // Aliases for backwards compatibility.
  // bind 做爲 on 別名,unbind 做爲 off 別名。(爲向後兼容)
  Events.bind   = Events.on;
  Events.unbind = Events.off;

  // Allow the `Backbone` object to serve as a global event bus, for folks who
  // want global "pubsub" in a convenient place.
  _.extend(Backbone, Events);


  // Backbone.Model(模型)
  // --------------------------

  // Backbone **Models** are the basic data object in the framework --
  // frequently representing a row in a table in a database on your server.
  // A discrete chunk of data and a bunch of useful, related methods for
  // performing computations and transformations on that data.
  // Create a new model with the specified attributes. A client id (`cid`)
  // is automatically generated and assigned for you.
  // 
  // 默認的 Model 構造函數主要作三件事:
  // 1. 爲實例設置 cid 屬性;
  // 2. 爲實例設置 attributes;
  // 3. 調用實例的 initialize 方法完成初始化。
  // 
  // 注意:
  // 若是給定 attributes,它是經過 set 方法添加到 model 的 attributes。
  // 而且是早於 initialize 方法調用。
  var Model = Backbone.Model = function(attributes, options) {
    var attrs = attributes || {};
    options || (options = {});
    // 生成惟一 cid,cid 表示 client id,是指在本地模型變量的標識,而不是模型所表明數據的惟一標識。
    this.cid = _.uniqueId(this.cidPrefix);
    this.attributes = {};
    // 若是指定了 collection,則直接綁定到模型上。
    if (options.collection) this.collection = options.collection;
    // 默認初始化設置 attributes 是不通過 parse 方法的(parse 方法只有在 fetch/save 等同步數據時才調用)
    // 若是指定 options.parse 爲真,則初始化時調用 parse 方法解析 attrs。
    if (options.parse) attrs = this.parse(attrs, options) || {};
    // 初始化 attributes 
    attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
    // 調用 set 方法設置初始屬性。
    // 不用擔憂 set 會觸發 change 事件,由於此時尚未調用 initialize 方法
    // 因此一般說來,此時你還來不及綁定任何事件。
    this.set(attrs, options);
    // 調用 set 方法會致使 this.changed 發生變化,
    // Jeremy Ashkenas 的意圖是初始化的 Model 不該該含有變化的屬性(由於一切都是初始的)
    // 因此須要從新將 this.changed 修改成空對象。
    // 注意:當設置初始 attributes 時,甚至都尚未調用 initialize。
    this.changed = {};
    // 調用初始化方法 initialize。
    this.initialize.apply(this, arguments);
  };

  // Attach all inheritable methods to the Model prototype.
  _.extend(Model.prototype, Events, {

    // 屬性哈希,用以保存模型發生變化的屬性哈希(只有 set 操做會產生舊屬性哈希)
    changed: null,

    // set 操做前驗證屬性哈希合法性,
    // 若是驗證失敗,本屬性保存驗證失敗的結果(model.validate 返回值),
    // 不然該屬性會被重置爲 null。
    validationError: null,

    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
    // CouchDB users may want to set this to `"_id"`.
    idAttribute: 'id',

    // The prefix is used to create the client id which is used to identify models locally.
    // You may want to override this if you're experiencing name clashes with model ids.
    cidPrefix: 'c',

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function(){},

    // 默認實現的 toJSON 方法是複製一份 attributes
    // 但你能夠覆寫該方法,該方法參數 options 或許用得上。
    // 該 options 與 fetch 方法的 options 參數相同,在未指定 HTTP 請求 data 時,
    // Backbone.sync 會默認使用 `model.toJSON(options)` 來生成 data。
    toJSON: function(options) {
      return _.clone(this.attributes);
    },

    // Proxy `Backbone.sync` by default -- but override this if you need
    // custom syncing semantics for *this* particular model.
    // 
    // 模型同步數據,默認委託 Backbone.sync 方法實現本方法。
    sync: function() {
      return Backbone.sync.apply(this, arguments);
    },

    // 獲取屬性值
    get: function(attr) {
      return this.attributes[attr];
    },

    // 獲取 HTML 轉義後的屬性值。
    escape: function(attr) {
      return _.escape(this.get(attr));
    },

    // 返回 true,若是模型指定 attribute 不爲 null 或 undefined。
    has: function(attr) {
      return this.get(attr) != null;
    },

    // 委託 _.iteratee 來判斷給定的 attrs 是不是模型 attributes 子集
    // 根據傳入 iteratee 參數不一樣,iteratee 具體實現也不一樣。
    // 1. attrs 爲 void 0。
    //  至關於 _.identity(this.attributes),返回結果爲 true。
    // 2. attrs 爲函數。
    //  至關於 attrs(this.attributes);
    // 3. attrs 爲對象。
    //  至關於 _.matcher(attrs)(this.attributes),判斷 attrs 是不是 attributes 子集。
    // 4. 其餘(主要是指 string)
    //  至關於 _.property(attrs)(this.attributes);
    matches: function(attrs) {
      return !!_.iteratee(attrs, this)(this.attributes);
    },

    // 設置模型屬性哈希,觸發 `change` 事件。
    // 本方法是模型對象的核心操做,更新模型數據並將屬性狀態變化通知給外部。
    // Backbone.Model 全部更新 attributes 的操做都是經過 set 方法完成,
    // 例如初始化 initialize(attributes), fetch, save 等。
    // 操做成功返回模型對象自身,操做失敗返回 false。
    // 注意:
    // options.slient 爲 true,只是表示本次 set 操做不觸發 `change` 事件。
    // 但仍然會更新模型的 `this.changed`, `this._previousAttributes` 屬性,
    // 所以在調用 `this.hasChanged()`, `this.changedAttributes()` ,`this.previous()`, `this.previousAttributes()` 方式時,
    // 仍然能夠識別中屬性哈希的變化。
    set: function(key, val, options) {

      // 未指定屬性名稱的操做屬於無效操做。
      // 例如:`model.set()` 或 `model.set(null, options)`。
      // 所以當須要調用 model.parse 方法時,
      // 返回值爲 null 或 undefined 將致使模型不設置任何屬性哈希。
      if (key == null) return this;

      // 將 `model.set(key, value, options)` 轉換爲 `model.set({key: value}, options)` 風格。
      var attrs;
      if (typeof key === 'object') {
        attrs = key;
        options = val;
      } else {
        (attrs = {})[key] = val;
      }

      options || (options = {});

      // 正式設置屬性哈希前,先驗證輸入參數。
      // 若是要求驗證數據,但驗證數據失敗,則停止 set 操做。
      // this._validate 方法是經過 this.validate 方法來實現的,
      // 只有定義了 this.validate 方法,纔會進行驗證,不然默認驗證成功。
      // 若是 this.validate 返回值爲真,則表示驗證失敗(返回值就是驗證失敗緣由 this.validationError),
      // 不然驗證成功,this.validationError 值設置爲 null。
      if (!this._validate(attrs, options)) return false;

      // Extract attributes and options.
      // 
      // 若是 unset 爲 true,則從 attributes 中移除 key。
      // 注意:
      // 只有 key 存在於 attributes 中,且 value 不等於 attributes[key] 時,
      // 當 key 被移除時纔會觸發 `change:key` 事件。
      var unset      = options.unset;   
      var silent     = options.silent;  // 若是爲 true,不觸發任何 `change` 事件。
      var changes    = [];   // 發生變化屬性名稱列表

      // 若是爲 true,表示模型處於 set 操做中。
      // 由於 set 操做能夠內嵌在 set 中,this._changing 至關於操做鎖。
      // 而局部變量 changing 能夠做爲主動 set 的標識,
      // 由於只有主動 set 的 changing 此時爲 false,而遞歸 set 中的 changing 都是 true。  
      var changing   = this._changing;  
      this._changing = true;

      // 若是 set 操做未鎖定,則設置相關屬性
      if (!changing) {
        this._previousAttributes = _.clone(this.attributes);   // 保存操做前的屬性哈希副本
        this.changed = {};  // (初始)設置變化屬性哈希
      }

      var current = this.attributes;   // 當前屬性哈希
      var changed = this.changed;  // 當前變化屬性哈希
      var prev    = this._previousAttributes;   // 操做前屬性哈希

      // 遍歷輸入哈希,更新或刪除哈希值
      for (var attr in attrs) {
        val = attrs[attr];
        // 當前屬性值不等於輸入屬性值時,在變化屬性名列表中記錄屬性名稱
        if (!_.isEqual(current[attr], val)) changes.push(attr);

        // 操做前屬性值不等於輸入屬性值時,記錄變化屬性值,不然移除變化屬性名。
        // (由於 set 能夠內嵌,this.changed 保存全部內嵌 set 操做結束後的屬性變化狀態)
        if (!_.isEqual(prev[attr], val)) {
          changed[attr] = val;
        } else {
          delete changed[attr];
        }
        
        // 若是 options.unset 爲真,則從當前屬性哈希中移除屬性,不然更新當前屬性哈希。
        unset ? delete current[attr] : current[attr] = val;
      }

      // 更新模型 id,由於 set 可能會更改 idAttribute 指定的主鍵值。
      this.id = this.get(this.idAttribute);

      // Trigger all relevant attribute changes.
      // 若是 set 不是靜默操做,則須要通知第三方自身屬性的變化。
      if (!silent) {
        // 若是變化屬性名稱列表不爲空,則逐一觸發 `change:key` 事件。
        // 而且將輸入 options 設置爲 this._pending。
        // `this._pending` 能夠用來緩存輸入 options,
        // 當在遞歸 set 中有屬性變化時,它能夠不斷被改寫。
        // 但只有在主動 set 中臨近操做結束時被讀取。
        if (changes.length) this._pending = options;
        for (var i = 0; i < changes.length; i++) {
          this.trigger('change:' + changes[i], this, current[changes[i]], options);
        }
      }

      // You might be wondering why there's a `while` loop here. Changes can
      // be recursively nested within `"change"` events.
      // 
      // changing 爲真,表示本次 set 爲遞歸操做,主動 set 操做還沒有結束,當即返回。
      if (changing) return this;

      // 本行如下代碼只有在主動 set 操做中才會執行。
      // 若是非靜默 set,則須要觸發 `change` 事件。
      if (!silent) {
        // 當 this._pending 爲真時,表示有屬性變化,須要觸發 `change` 事件。
        // 而且 this._pending 值就是輸入參數 options。
        while (this._pending) {
          options = this._pending;
          this._pending = false;
          this.trigger('change', this, options);
        }
      }
      this._pending = false;  // 重置爲 false 表示屬性沒有變化了。
      this._changing = false;  // 設置爲 false 表示主動 set 操做結束。
      return this;
    },

    // 從模型屬性哈希中移除屬性,並觸發 `change` 事件。
    // (經過調用 set 方法實現)
    unset: function(attr, options) {
      return this.set(attr, void 0, _.extend({}, options, {unset: true}));
    },

    // 從模型屬性哈希中移除全部屬性,觸發 `change` 事件。
    // (經過調用 set 方法實現)
    clear: function(options) {
      var attrs = {};
      for (var key in this.attributes) attrs[key] = void 0;
      return this.set(attrs, _.extend({}, options, {unset: true}));
    },

    // 判斷模型對象屬性哈希在最後一次 `set` 操做時,是否發生了變化。
    // 或者判斷指定的屬性在最後一次 `set` 操做時是否發生了變化。
    // 在 set 操做時,使用 options.silent = true 不影響本函數的判斷結果。
    hasChanged: function(attr) {
      if (attr == null) return !_.isEmpty(this.changed);
      return _.has(this.changed, attr);
    },

    // 本方法有兩個用途:
    // 1. 當不傳入任何參數時(或 diff 爲否),判斷最後一次 set 操做,屬性哈希是否發生變化。
    // 若是發生變化,返回變化屬性哈希,不然返回 false。
    // 
    // 2. 傳一個 Object 對象做爲 diff 參數,將其與模型當前屬性哈希進行對比,
    // 篩選出於不一樣於當前屬性哈希的屬性,若是有篩選結果,則返回篩選結果,不然返回 false。
    // 使用 `model.changedAttributes(someObject)` 能夠(預先)判斷出 set 哪些值會致使模型的屬性哈希發生變化。
    changedAttributes: function(diff) {
      if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
      var old = this._changing ? this._previousAttributes : this.attributes;
      var changed = {};
      for (var attr in diff) {
        var val = diff[attr];
        if (_.isEqual(old[attr], val)) continue;
        changed[attr] = val;
      }
      return _.size(changed) ? changed : false;
    },

    // 返回最後一次 set 以前的指定屬性值(不管該屬性是否發生過變化)。
    // 不傳入參數,或者模型沒有進行過 set 操做,返回 null;
    previous: function(attr) {
      if (attr == null || !this._previousAttributes) return null;
      return this._previousAttributes[attr];
    },

    // 返回最後一次 set 前的屬性哈希。
    // 若是沒有 set 過,則返回 null。
    previousAttributes: function() {
      return _.clone(this._previousAttributes);
    },

    // Fetch the model from the server, merging the response with the model's
    // local attributes. Any changed attributes will trigger a "change" event.
    // fetch 方法主要用於從遠端讀取數據同步到本地 attributes,若是屬性值發生變化,觸發 `change` 事件。
    // Backbone 本意是爲 REST API 而設計,但也能夠兼容非 REST API。
    // 使用非 REST API 時,應該改寫 parse 方法,再調用 fetch 方法。
    fetch: function(options) {
      // 遠端的響應結果默認須要經過 parse 方法解析才能 set,
      // 能夠在 options 中指定 parse 爲 false 來解除這一邏輯。
      options = _.extend({parse: true}, options);
      var model = this;

      // 封裝 success 操做
      // 不管 options 中是否指定 success 回調,xhr 請求成功後都會有一次 success 回調。
      // 若是有 options.success 回調函數,回調函數會在封裝的 success 回調用執行。
      var success = options.success;
      options.success = function(resp) {
        // 若是要求 parse 爲真,則遠程返回值必須通過 parse 方法解析,不然遠程返回值就是響應數據。
        var serverAttrs = options.parse ? model.parse(resp, options) : resp;
        // model.set 方法只有在 validate 失敗時纔會返回 false。
        // 若是驗證失敗,則不會進行具備實際意義的 set 操做。
        // 而且觸發 invalid 事件。
        if (!model.set(serverAttrs, options)) return false;
        // 若是 set 操做成功,則繼續調用本來計劃的 success 回調函數。
        // 注意:
        //    此處的 success 回調與原生 jQuery ajax success 回調稍微不一樣的是,
        //    它的上下文由 options.context 指定。
        if (success) success.call(options.context, model, resp, options);
        // 執行完 success 回調後觸發 sync 事件。
        model.trigger('sync', model, resp, options);
      };
      // 封裝 options.error 回調,確保 xhr 失敗時,觸發 model 的 error 事件。
      wrapError(this, options);
      // read 遠程數據,默認使用 this.sync 方法實現,
      // this.sync 默認使用 Backbone.sync 方法實現,
      // Backbone.sync 默認使用 Backbone.$ 方法實現,
      // Backbone.$ 默認使用 jQuery.ajax 方法實現。
      return this.sync('read', this, options);
    },

    // Set a hash of model attributes, and sync the model to the server.
    // If the server returns an attributes hash that differs, the model's
    // state will be `set` again.
    // 
    // 設置 model 的 attributes,而且將 attributes 同步到遠端。
    // 若是遠端返回的響應值(經過 parse 方法解析後)不一樣於 attributes,
    // 則再次執行 set 操做。
    save: function(key, val, options) {
      // Handle both `"key", value` and `{key: value}` -style arguments.
      // 處理不一樣傳參方式。
      var attrs;
      if (key == null || typeof key === 'object') {
        attrs = key;
        options = val;
      } else {
        (attrs = {})[key] = val;
      }

      // 默認要求進行 validate 和 parse 操做。
      options = _.extend({validate: true, parse: true}, options);
      // 是否等待服務器響應再進行 set 操做的標識。(默認不等待)
      // 等待服務器響應與否的區別是:
      //  一個先 set,後同步數據。
      //  一個是先同步數據,而後 set。
      var wait = options.wait;

      // If we're not waiting and attributes exist, save acts as
      // `set(attr).save(null, opts)` with validation. Otherwise, check if
      // the model will be valid when the attributes, if any, are set.
      // 若是 attrs 爲真,且無需等待服務器響應,則當即使用 attrs 進行 set 操做。
      // 注意:
      //    若是不等待服務器響應,set 操做一旦成功會當即觸發 `change` 事件,
      //    但隨後的服務器響應值會被從新 set 一次,有可能會 set 失敗。
      if (attrs && !wait) {
        // 若是 set 操做失敗(即 validate 失敗),當即返回 false(結束 save 操做)。
        // 注意:什麼要求 attrs 也爲真才進行 set 操做?
        // 若是不限制 attrs 爲真,那麼 set 操做會默認成功,則將致使 save 操做不會終止。
        // 那麼 save 會將未作任何修改的 attributes 再次同步到遠端,這樣不符合 save 操做的意圖。 
        if (!this.set(attrs, options)) return false;
      } else {
        // 驗證 attrs,驗證失敗則當即終止 save 操做。
        if (!this._validate(attrs, options)) return false;
      }

      // After a successful server-side save, the client is (optionally)
      // updated with the server-side state.
      // 
      // 如下邏輯與 fetch 邏輯類似,開始進行數據同步相關操做。
      var model = this;
      var success = options.success;
      var attributes = this.attributes;
      // 封裝 success 回調
      options.success = function(resp) {
        // Ensure attributes are restored during synchronous saves.
        model.attributes = attributes;
        // 解析遠端響應值
        var serverAttrs = options.parse ? model.parse(resp, options) : resp;
        // 若是當前 save 操做是須要等待服務器響應的,則合併 attrs 和 serverAttrs 屬性,
        // 而後再進行 set 操做。
        if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
        // 不管是否要求 save 操做等待服務器響應,若是響應值存在(通過可能的 parse 操做後,解析結果不爲 null 或 undefined),
        // 則進行 set 操做,set 操做失敗(驗證失敗)會當即終止 save 操做。
        if (serverAttrs && !model.set(serverAttrs, options)) return false;
        // set 成功後,執行可能計劃的 success,而後觸發 sync 事件。
        if (success) success.call(options.context, model, resp, options);
        model.trigger('sync', model, resp, options);
      };
      // 封裝 error 回調
      wrapError(this, options);

      // Set temporary attributes if `{wait: true}` to properly find new ids.
      // 設置臨時的 attributes,由於在 Backbone.sync 操做中可能須要將 attributes 同步到遠端。
      // 注意:此處是直接修改 attributes,而不是經過 set 操做進行修改,所以不會觸發任何事件。
      if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);

      // 根據模型狀態選擇 REST API 的提交方式
      var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
      // 若是是 patch 操做,則將 attrs 保存在 options.attrs 中
      // (此處保存 attrs 的意圖不是很明確,難道只是爲了記錄下 patch 的數據,以便知足開發者的個性化操做?)
      if (method === 'patch' && !options.attrs) options.attrs = attrs;
      var xhr = this.sync(method, this, options);

      // Restore attributes.
      // 馬上恢復模型應該擁有的 attributes。
      this.attributes = attributes;

      return xhr;
    },

    // Destroy this model on the server if it was already persisted.
    // Optimistically removes the model from its collection, if it has one.
    // If `wait: true` is passed, waits for the server to respond before removal.
    // 銷燬模型(並同步從遠端銷燬)
    // 若是 options.wait 爲真,則等待遠端同步成功後,再銷燬模型。
    // destroy 操做主要實現:
    //    1. stopListening 全部事件(不包括自身的 on 事件)
    //    2. 觸發 destroy 事件(通知 collection 將本身從 collection 中移除)
    //    3. (可選)同步從遠端刪除數據。(根據 model.isNew() 判斷是否要觸發 sync 事件)
    //    
    // 注意:
    //  Backbone.sync 期待的是 RESTFUL API,若是使用 emulatedHTTP,
    //  destroy 操做 success 會在 XHR 請求成功後當即執行,
    //  即 XHR 請求成功,即視爲遠端刪除數據成功。
    //  所以在不重寫 destroy 的方法前提下,要求遠端接口響應必需以 HTTP Status Code(200 或 404)做爲操做成功失敗的標識,
    //  而不能在響應的 data 中約定操做成功失敗代號。
    destroy: function(options) {
      options = options ? _.clone(options) : {};
      var model = this;
      var success = options.success;
      var wait = options.wait;

      // 銷燬模型(中止監聽事件,觸發 destroy 事件)
      var destroy = function() {
        model.stopListening();
        model.trigger('destroy', model, model.collection, options);
      };

      // 封裝 success 回調
      // 該回調會在請求成功後當即執行,請求成功即被視爲操做成功。
      options.success = function(resp) {
        if (wait) destroy();
        if (success) success.call(options.context, model, resp, options);
        // 若是 model.isNew() 爲假,纔有可能會觸發 sync 事件。
        if (!model.isNew()) model.trigger('sync', model, resp, options);
      };

      var xhr = false;
      // 若是模型數據不存在於遠端(按照 Backbone 設計理論),
      // 則無需與遠端進行數據同步操做,直接執行 success 回調。(理論上也不觸發 sync 事件)
      if (this.isNew()) {
        _.defer(options.success);
      } else {
        // 封裝異常回調
        wrapError(this, options);
        // 與遠端同步
        xhr = this.sync('delete', this, options);
      }
      // 若是不等待,則當即銷燬模型。
      if (!wait) destroy();
      return xhr;
    },

    // Default URL for the model's representation on the server -- if you're
    // using Backbone's restful methods, override this to change the endpoint
    // that will be called.
    // 生成 model 進行 sync 時的 URL(適用於 RESTFUL API)
    // 默認的 url 方法主要適用於 RESTFUL API,自動生成 URL。
    // 對於非 RESTFUL API,最好重寫該方法。
    url: function() {
      var base =
        _.result(this, 'urlRoot') ||
        _.result(this.collection, 'url') ||
        urlError();
      if (this.isNew()) return base;
      var id = this.get(this.idAttribute);
      // 自動補齊 base 末尾的 `/` 符號,而後追加 id
      return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
    },

    // parse 方法存在的意義在於解析遠程同步數據時,遠端返回的響應對象,
    // 該對象默認是 REST API 返回的數據對象,所以能夠直接被 set 方法使用。
    // 但對於非 REST API 接口響應對象,則須要調用 parse 將其響應結果解析後再返回給 set 使用。
    // 因此說,若是直接使用 set 方法設置屬性,是無需通過 parse 方法的,只有自動同步遠程數據時才須要覆寫該方法。 
    // 例如 fetch 方法中調用了 parse 方法解析遠端的響應值,fetch 方法中使用 parse 解析結果去作 set 操做,
    // 所以 parse 返回 null 或 undefined 時,set 操做會當即終止。
    parse: function(resp, options) {
      return resp;
    },

    // Create a new model with identical attributes to this one.
    clone: function() {
      return new this.constructor(this.attributes);
    },

    // A model is new if it has never been saved to the server, and lacks an id.
    // 判斷一個 model 是否從未保存到遠端。
    // 判斷依據是查看該 model 的 attributes 是否擁有 this.idAttribute 映射字段。
    // Backbone.Model 的設計意圖是 Model 是遠端一條數據的抽象對象(例如數據庫中某張表裏某一行數據),
    // 每一個 model 都應該擁有一個主鍵(對應數據庫裏數據行的主鍵值),擁有主鍵則表示遠端已存在該條數據,
    // 不然視該 model 爲未保存的數據模型。
    isNew: function() {
      return !this.has(this.idAttribute);
    },

    // Check if the model is currently in a valid state.
    // 檢查 model 當前的 attributes 是否處於合法狀態(可以經過驗證)
    isValid: function(options) {
      return this._validate({}, _.defaults({validate: true}, options));
    },

    // Run validation against the next complete set of model attributes,
    // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
    // 注意:
    // validate 是對模擬 set 成功後的 attributes 進行驗證,而不只僅是對 attrs 進行驗證。
    // 也就是說 this.validate(attrs, options) 中的 attrs 是指模擬 set 成功後的 attributes。
    _validate: function(attrs, options) {
      if (!options.validate || !this.validate) return true;
      attrs = _.extend({}, this.attributes, attrs);
      var error = this.validationError = this.validate(attrs, options) || null;
      if (!error) return true;
      this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
      return false;
    }

  });

  // Underscore methods that we want to implement on the Model, mapped to the
  // number of arguments they take.
  var modelMethods = { keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
      omit: 0, chain: 1, isEmpty: 1 };

  // Mix in each Underscore method as a proxy to `Model#attributes`.
  addUnderscoreMethods(Model, modelMethods, 'attributes');

  // Backbone.Collection
  // -------------------

  // If models tend to represent a single row of data, a Backbone Collection is
  // more analogous to a table full of data ... or a small slice or page of that
  // table, or a collection of rows that belong together for a particular reason
  // -- all of the messages in this particular folder, all of the documents
  // belonging to this particular author, and so on. Collections maintain
  // indexes of their models, both in order, and for lookup by `id`.

  // Create a new **Collection**, perhaps to contain a specific type of `model`.
  // If a `comparator` is specified, the Collection will maintain
  // its models in sort order, as they're added and removed.
  // 
  // Collection 構造函數,能夠指定 collection 的 model 類型。
  // 若是給定 `comparator`,當天新增或移除 model 時,collection 會自動維護 models 的排序。
  var Collection = Backbone.Collection = function(models, options) {
    options || (options = {});
    // 若是 options 中包含 model 字段,則直接綁定到 collection。
    if (options.model) this.model = options.model;
    // 若是 options 中包含 comparator 且不爲 undefined,則直接綁定到 collection。
    if (options.comparator !== void 0) this.comparator = options.comparator;
    // 重置 collection 的 length, models, _byId 三個屬性。
    this._reset();
    // 初始化 collection
    this.initialize.apply(this, arguments);
    // 若是指定了 models, 則靜默設置初始 models
    // todo: 
    //   與 model 初始化不一樣,collection 使用 reset 而不是 set 做爲構造初始數據的手段,
    //   且 reset 操做晚於 initialize 操做。做者意圖不是很明確。
    //   如此操做的話,則意味着你不該該在 initialize 中對 collection 進行成員增減操做,
    //   不然可能會在構造實例時,被構造參數中的 models 覆寫了 collection 成員。
    if (models) this.reset(models, _.extend({silent: true}, options));
  };

  // collection#set 操做的默認選項。
  var setOptions = {add: true, remove: true, merge: true};
  var addOptions = {add: true, remove: false};

  // 將數組 insert 成員,依次插入到數組 array 的 at 位置。
  // 例如:
  // var a = [1,2,3], b = [4,5,6];
  // splice(a, b, 1);
  // 數組 a 變成 [1, 4, 5, 6, 2, 3]
  var splice = function(array, insert, at) {
    // 確保 at 是符合 array 長度的合法位置(不小於 0,不大於 array 長度)。
    at = Math.min(Math.max(at, 0), array.length);
    // 生成切片後半部分等長 Array。
    var tail = Array(array.length - at);
    // 計算待插入 Array 長度
    var length = insert.length;
    // 將 array 後半部分紅員複製到容器 tail。
    for (var i = 0; i < tail.length; i++) tail[i] = array[i + at];
    // 將 insert 成員依次插入到 array 的後半部分。  
    for (i = 0; i < length; i++) array[i + at] = insert[i];
    // 將 tail 中成員依次繼續插入到 array 尾部。
    for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
  };

  // Define the Collection's inheritable methods.
  _.extend(Collection.prototype, Events, {

    // 默認 model 爲 Backbone.Model。
    // 大部分情景中你須要重寫該屬性。
    model: Model,

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function(){},

    // 返回一個數組,成員是 collection 中每一個 model 的 JSON 值。
    toJSON: function(options) {
      return this.map(function(model) { return model.toJSON(options); });
    },

    // Proxy `Backbone.sync` by default.
    sync: function() {
      return Backbone.sync.apply(this, arguments);
    },

    // Add a model, or list of models to the set. `models` may be Backbone
    // Models or raw JavaScript objects to be converted to Models, or any
    // combination of the two.
    // 
    // 使用 set 操做往 collection 添加一個或多個成員。
    // models 能夠是 Backbone.Model 及其子類實例,或者是純 Object,或者兩者混合組成的數組。
    // 關於 options:
    //    默認 merge 爲 false,但容許指定爲 true。
    //    強制 add 爲 true,remove 爲 false,不容許修改。
    // 
    // add 操做的默認行爲是在 collection 末尾追加成員,若是成員已經存在,則不追加。
    add: function(models, options) {
      return this.set(models, _.extend({merge: false}, options, addOptions));
    },

    // Remove a model, or a list of models from the set.
    // 
    // 從 collection 中移除一個或一組成員。
    remove: function(models, options) {
      options = _.extend({}, options);
      var singular = !_.isArray(models);
      models = singular ? [models] : _.clone(models);
      // 移除成員
      var removed = this._removeModels(models, options);
      // 若是 remove 操做非靜默,而且的確移除了成員,
      // 觸發 update 事件。
      if (!options.silent && removed) this.trigger('update', this, options);
      // 返回被移除的成員(們)。
      return singular ? removed[0] : removed;
    },

    // Update a collection by `set`-ing a new list of models, adding new ones,
    // removing models that are no longer present, and merging models that
    // already exist in the collection, as necessary. Similar to **Model#set**,
    // the core operation for updating the data contained by the collection.
    // 
    // 該方法是 collection 操做 models 的核心方法,重要性等同於 Model#set 方法。
    // 該方法用以設置一組新的成員,添加新成員,刪除再也不具備成員資格的成員,合併已存在的成員。
    // 
    // options:
    //  add: 若是 model 存在於 models 中但不存在於 collection 中,是否要往 collection 中添加該 model。(默認爲真)
    //  remove: 若是 model 存在於 collection 中但不存在於 models 中,是否要從 collection 中刪除該 model。(默認爲真)
    //  merge: 若是 model 存在於 models 中同時也存在於 collection 中,是否要將兩者進行合併。(默認爲真)
    //  silent: 是否要觸發事件(默認爲真)
    //  sort: 是否要自動排序(默認爲真)
    //  parse: set 操做前是否要通過 parse 方法解析,包括經過純 Object 生成 Model 實例時,是否要調用 Model.parse 解析(默認爲假)
    //  
    //  collection#set 操做的本質是,將目標 models 數組中的數據,合併到內部 models 數組中,
    //  兩個數組的數據合併,涉及到求數據交集、求數據並集、是否合併數據的問題。set 操做就是實現了這三個問題的解決方法。
    //  在全部對 models 操做過程當中,collection 始終保持對實例 models 的引用一致性(即歷來沒有更換過 models 數組的指針)
    //  
    //  set 操做中,會對根據每一個新增的成員和移除的成員依次觸發 add 和 remove 事件。
    //  因此雖然 set 操做能夠經過 options 中 add 和 remove 的值,來實現置換整個 collection.models 內部全部成員,
    //  但你的意圖是徹底置換而不想逐一觸發 add 或 remove 事件,那麼最好使用 collection#reset 操做,該操做只會觸發一個 `reset` 事件。
    //  
    //  若是 models 爲 null 或 void 0,會致使 set 操做終止。
    //  但若是 parse 方法返回值爲 null 或 void 0,或者 parse 方法返回的數組中包含 null 或 void 0,都會被視爲一個合法成員,
    //  collection 會首先尋找該成員是否存在,若是不存在則視爲新成員,使用 this.model 來構造新的實例,因此若是該 `成員` 爲 null 或 void 0,
    //  新實例也會被構造出來並可能被添加到 collection(除非 model 實例在構造時未能經過合法性驗證)。
    //  
    //  注意:
    //  options.parse 對 collection#set 方法有效,而對 model#set 方法無效。
    set: function(models, options) {
      // 若是 models 爲 null 或 undefined,終止 set 操做。
      if (models == null) return;

      // 準備 options,默認 add: true, remove: true, merge: true。
      options = _.defaults({}, options, setOptions);
      // 若是 options.parse 爲真,且 models 非 Backbone.Model 實例,
      // 則調用 this.parse 方法對 models 進行解析。
      if (options.parse && !this._isModel(models)) models = this.parse(models, options);

      // 若是 models 不是數組,則將其轉換爲數組。
      // 注意:
      //    此處操做實際上是讀取了 models 副本,而非原始 models,
      //    以免後面對 models 的操做會影響到輸入的 models。
      var singular = !_.isArray(models);
      models = singular ? [models] : models.slice();

      var at = options.at;  // 插入新成員的位置
      if (at != null) at = +at;  // 將 at 強轉爲數字類型
      if (at < 0) at += this.length + 1;  // 若是 at 是負數,則表示位置是倒數的,將其轉換爲實際位置。

      var set = [];  // 置換的成員容器
      var toAdd = [];   // 新添加的成員容器
      var toRemove = [];  // 待刪除的成員容器
      var modelMap = {};  // 置換成員映射表

      var add = options.add;   // 新增標識
      var merge = options.merge;   // 合併標識
      var remove = options.remove;  // 移除標識

      var sort = false;   // 是否須要排序
      // 是否具有排序條件(必需定義了 comparator,不能指定插入位置,沒有顯式聲明不排序)
      var sortable = this.comparator && (at == null) && options.sort !== false;  
      var sortAttr = _.isString(this.comparator) ? this.comparator : null;  // 若是 comparator 是字符串,則表示使用 model 某個屬性做爲排序因子

      // Turn bare objects into model references, and prevent invalid models
      // from being added.
      var model;
      // 遍歷 models,處理那些須要被添加到 collection 的 model
      for (var i = 0; i < models.length; i++) {
        model = models[i];

        // If a duplicate is found, prevent it from being added and
        // optionally merge it into the existing model.
        // 查找待添加 model 是否已存在於 collection 中。
        var existing = this.get(model);
        if (existing) {
          // 若是 collection 已保存有目標 model,而且待添加 model 不等於已存在 model。
          // 即待添加的是另外一個模型實例或者 Object,且 merge 爲真,則合併新的 model。
          if (merge && model !== existing) {
            // 若是待添加 model 爲 Backbone.Model 實例,獲取它的 attributes 做爲 attrs。
            var attrs = this._isModel(model) ? model.attributes : model;
            // 若是 parse 爲真,則須要調用 existing 的 parse 方法來解析 attrs,
            // 以後才能對 existing 進行 set 操做。
            if (options.parse) attrs = existing.parse(attrs, options);
            existing.set(attrs, options);
            // 若是具有排序條件,而且沒有排序,則從新設定 sort 以標識是否須要排序。
            // 若是 existing 中做爲排序的因子屬性發生了變化,則須要將 sort 設置爲真,表示要排序。
            if (sortable && !sort) sort = existing.hasChanged(sortAttr);
          }
          if (!modelMap[existing.cid]) {
            modelMap[existing.cid] = true;
            set.push(existing);
          }
          // 將 models 中待添加的 model 替換爲 existing。
          models[i] = existing;

        // If this is a new, valid model, push it to the `toAdd` list.
        } else if (add) {
          // 若是 options.add 爲真,則準備一個待處理的 model。
          model = models[i] = this._prepareModel(model, options);
          // model 只能是一個 Model 實例或者 false,
          // 若是是 false,則表示該 model 不是一個合法的 model,直接忽略。
          if (model) {
            toAdd.push(model);
            // 添加 model 與 collection 之間的引用關係
            this._addReference(model, options);
            modelMap[model.cid] = true;
            set.push(model);
          }
        }
      }

      // Remove stale models.
      // 若是 options.remove 爲真,則須要移除 collection.models 中多餘的 model。
      if (remove) {
        // 遍歷 this.models,篩選中待移除的 model,保存在 toRemove 中
        for (i = 0; i < this.length; i++) {
          model = this.models[i];
          // 若是置換成員映射中不包含該 model,則表示它須要被移除。
          if (!modelMap[model.cid]) toRemove.push(model);
        }
        // 移除多餘的 model。
        if (toRemove.length) this._removeModels(toRemove, options);
      }

      // See if sorting is needed, update `length` and splice in new models.
      // 標識成員的順序是否發生變化,該標識主要充當是否觸發 sort 事件的條件因子。
      var orderChanged = false;  
      // 是否直接置換 collection.models
      var replace = !sortable && add && remove;  
      if (set.length && replace) {
        // 若是置換的成員數量與現有成員數量不符,或者任意置換成員與現有成員位置不符,則表示須要從新排序。
        orderChanged = this.length != set.length || _.some(this.models, function(model, index) {
          return model !== set[index];
        });
        // 清空現有全部成員
        this.models.length = 0;
        // 插入置換成員
        splice(this.models, set, 0);
        // 實時維護 length 屬性
        this.length = this.models.length;
      } else if (toAdd.length) {
        // 若是具有排序條件,sort 設置爲 true
        if (sortable) sort = true;
        // 將新的成員插入到 this.models,若是未指定插入位置,則從最末尾插入。
        splice(this.models, toAdd, at == null ? this.length : at);
        // 實時維護 collection.length 屬性
        this.length = this.models.length; 
      }

      // Silently sort the collection if appropriate.
      // 若是須要排序,則靜默排序(阻止排序過程當中觸發 sort 事件)
      if (sort) this.sort({silent: true});

      // Unless silenced, it's time to fire all appropriate add/sort events.
      // 如今來處理一下事件的事情,
      // 若是如非靜默操做,須要依次觸發可能存在的 add, sort, update 事件。
      if (!options.silent) {
        for (i = 0; i < toAdd.length; i++) {
          if (at != null) options.index = at + i;
          model = toAdd[i];
          model.trigger('add', model, this, options);
        }
        if (sort || orderChanged) this.trigger('sort', this, options);
        if (toAdd.length || toRemove.length) this.trigger('update', this, options);
      }

      // Return the added (or merged) model (or models).
      // 返回單個 model 或 models
      return singular ? models[0] : models;
    },

    // When you have more items than you want to add or remove individually,
    // you can reset the entire set with a new list of models, without firing
    // any granular `add` or `remove` events. Fires `reset` when finished.
    // Useful for bulk operations and optimizations.
    // 使用 reset 操做替代 set 操做來重置整個 collection.models,避免觸發 add 或 remove 事件,
    // 只有一個 `reset` 事件。
    // 注意:
    //  reset 操做會簡單地從新生成一個空數組,並將該數組指針賦值給 collection.models,
    //  而以前的 collection.models 會保留在 options.previousModels 做爲參數傳遞給 reset 事件。
    // 
    // reset 與 set 不一樣之處在於,set 操做維持 collection.models 指針不變,而 reset 會更換 collection.models 指針。
    reset: function(models, options) {
      options = options ? _.clone(options) : {};
      // 遍歷現有成員,逐一銷燬成員與集合之間的引用關係
      for (var i = 0; i < this.models.length; i++) {
        this._removeReference(this.models[i], options);
      }
      // 保留以前的 models 引用
      options.previousModels = this.models;
      // 重置內部狀態(包括更換 this.models)
      this._reset();
      // 調用 add 操做添加成員(add 操做內部是調用 set 操做)
      models = this.add(models, _.extend({silent: true}, options));
      // 觸發 reset 事件
      if (!options.silent) this.trigger('reset', this, options);
      return models;
    },

    // Add a model to the end of the collection.
    // 其實 push 等同於 add,你也能夠在 options 中指定 at 做爲插入位置。
    // 並且 model 能夠是單個也能夠是多個。
    push: function(model, options) {
      return this.add(model, _.extend({at: this.length}, options));
    },

    // Remove a model from the end of the collection.
    // 移除最後一個成員。
    pop: function(options) {
      var model = this.at(this.length - 1);
      return this.remove(model, options);
    },

    // Add a model to the beginning of the collection.
    // 在內部 models 數組頭部追加成員。
    // 等同於 add 操做,能夠經過 options.at 參數修改 unshift 行爲
    unshift: function(model, options) {
      return this.add(model, _.extend({at: 0}, options));
    },

    // Remove a model from the beginning of the collection.
    // 移除第一個成員
    shift: function(options) {
      var model = this.at(0);
      return this.remove(model, options);
    },

    // Slice out a sub-array of models from the collection.
    // 對 this.models 進行切片操做。
    slice: function() {
      return slice.apply(this.models, arguments);
    },

    // Get a model from the set by id.
    // 查找成員,obj 能夠是一個 id 值,
    // 或者是一個包含 collection.model.prototype.idAttribute 屬性的對象,
    // 或者是一個 model 實例。
    // collection 首先嚐試使用 id 查找,而後使用 cid 查找。
    get: function(obj) {
      if (obj == null) return void 0;
      var id = this.modelId(this._isModel(obj) ? obj.attributes : obj);
      return this._byId[obj] || this._byId[id] || this._byId[obj.cid];
    },

    // Get the model at the given index.
    // 獲取指定位置的成員,若是 at 爲負數,表示倒數位置。
    at: function(index) {
      if (index < 0) index += this.length;
      return this.models[index];
    },

    // Return models with matching attributes. Useful for simple cases of
    // `filter`.
    // 查找成員
    where: function(attrs, first) {
      return this[first ? 'find' : 'filter'](attrs);
    },

    // Return the first model with matching attributes. Useful for simple cases
    // of `find`.
    // 查找成員
    findWhere: function(attrs) {
      return this.where(attrs, true);
    },

    // Force the collection to re-sort itself. You don't need to call this under
    // normal circumstances, as the set will maintain sort order as each item
    // is added.
    // 
    // 強制對 collection 成員進行排序,但若是沒有聲明 comparator,則拋出異常。
    sort: function(options) {
      var comparator = this.comparator;
      if (!comparator) throw new Error('Cannot sort a set without a comparator');
      options || (options = {});

      // length 變量記錄 comparator 長度,主要意圖是記錄 comparator 做爲函數時,期待參數的個數。
      var length = comparator.length;
      if (_.isFunction(comparator)) comparator = _.bind(comparator, this);

      // Run sort based on type of `comparator`.
      // 若是 comparator 是一個接受單個參數的函數,或者字符串,
      // 則使用 sortBy 進行(升序)排序,不然對 models 進行原生數組排序。
      if (length === 1 || _.isString(comparator)) {
        this.models = this.sortBy(comparator);
      } else {
        this.models.sort(comparator);
      }
      // 若是 sort 爲非靜默操做,則觸發 sort 事件
      if (!options.silent) this.trigger('sort', this, options);
      return this;
    },

    // Pluck an attribute from each model in the collection.
    // 獲取每一個成員指定的 attribute。
    // 注意:這裏使用 get 方法獲取屬性,而不是直接使用 _.pluck(this.toJSON(), attr);
    // 這樣作避免了直接讀取 model.attributes,若是 model 的 get 方法被改寫了,也能夠正確返回相應的值。
    pluck: function(attr) {
      return _.invoke(this.models, 'get', attr);
    },

    // Fetch the default set of models for this collection, resetting the
    // collection when they arrive. If `reset: true` is passed, the response
    // data will be passed through the `reset` method instead of `set`.
    // 
    // 與 Model#fetch 方法相似,若是 options.reset 爲真,則使用 collection.reset 處理遠端響應,
    // 不然使用 collection.set 處理遠端響應。
    fetch: function(options) {
      options = _.extend({parse: true}, options);
      var success = options.success;
      var collection = this;
      options.success = function(resp) {
        var method = options.reset ? 'reset' : 'set';
        collection[method](resp, options);
        if (success) success.call(options.context, collection, resp, options);
        collection.trigger('sync', collection, resp, options);
      };
      wrapError(this, options);
      return this.sync('read', this, options);
    },

    // Create a new instance of a model in this collection. Add the model to the
    // collection immediately, unless `wait: true` is passed, in which case we
    // wait for the server to agree.
    // 經過 Model#save 方法實現的建立 model。
    create: function(model, options) {
      options = options ? _.clone(options) : {};
      var wait = options.wait;
      // 準備 model,若是準備 model 失敗,則直接終止 create 操做,並返回 false。
      model = this._prepareModel(model, options);
      if (!model) return false;
      // 若是不等待服務器響應,則直接添加 model 到 collection.models。
      // 這意味着,不管 model 是否 validate 與否,它都會被添加到 collection 中。
      // 由於 model 實例化過程當中,不管 validate 成功失敗,都不能阻止 model 構造完成。
      // 而等待服務器響應,在 model.save 過程當中,能夠對 attributes 進行合法性驗證,
      // 從而阻止 options.success 被調用,也就阻止了非法的 model 被添加到 collection 中。
      if (!wait) this.add(model, options);
      // 不然等到服務器響應成功後再將 model 添加到 collection
      var collection = this;
      var success = options.success;
      options.success = function(model, resp, callbackOpts) {
        if (wait) collection.add(model, callbackOpts);
        if (success) success.call(callbackOpts.context, model, resp, callbackOpts);
      };
      // 經過 model.save 方法實現 create,即 colleciton 自己是不負責真正的 model 數據同步。
      model.save(null, options);
      return model;
    },

    // **parse** converts a response into a list of models to be added to the
    // collection. The default implementation is just to pass it through.
    // 
    // 本方法將遠端響應轉換爲一個列表(待添加成員列表)或者是一個成員對象。
    // parse 返回任何對象(包括 null, undefined),若是不是數組,都會被轉換爲數組,
    // 而後被 collection 用做查找已存在成員的因子,或者做爲 this.model 構造函數的 attributes 參數。
    parse: function(resp, options) {
      return resp;
    },

    // Create a new collection with an identical list of models as this one.
    clone: function() {
      return new this.constructor(this.models, {
        model: this.model,
        comparator: this.comparator
      });
    },

    // Define how to uniquely identify models in the collection.
    // 
    // 該方法主要用於讓 collection 給每一個成員生成一個惟一標識。
    // collection 內部須要斷定成員身份的操做都須要調用該方法。
    modelId: function (attrs) {
      return attrs[this.model.prototype.idAttribute || 'id'];
    },

    // 私有方法,重置 collection 內部狀態(主要是 collection 的 length, models, _byId)。
    // 只有在 collection 進行初始化或 reset 操做時才調用該方法。
    _reset: function() {
      // Collection 是實時維護 length 屬性,
      // 而不是經過 this.models.length 求值來獲取成員長度。
      this.length = 0;
      this.models = [];
      this._byId  = {};
    },

    // Prepare a hash of attributes (or other model) to be added to this
    // collection.
    // 
    // 
    _prepareModel: function(attrs, options) {
      // 若是 attrs 是一個 Backbone.Model 實例,且該模型未屬於其餘 collection,則爲其添加 collection 屬性。
      // 這意味着一個 model 不能同時關聯到兩個 collection。
      if (this._isModel(attrs)) {
        if (!attrs.collection) attrs.collection = this;
        return attrs;
      }
      // 若是 attrs 不是 Backbone.Model 實例,
      // 則使用 this.model 做爲構造函數,構造一個 Backbone.Model 實例。
      options = options ? _.clone(options) : {};
      options.collection = this;
      var model = new this.model(attrs, options);
      // 若是構造 model 實例過程當中,沒有發生數據驗證失敗,
      // 則表示新構造的 model 是一個合法的 model,直接返回該 model。
      // 不然在 collection 觸發 invalid 事件,並返回 false。
      if (!model.validationError) return model;
      this.trigger('invalid', this, model.validationError, options);
      return false;
    },

    // Internal method called by both remove and set.
    // 
    // 私有方法,在 remove 和 set 操做中調用,用以移除成員。
    _removeModels: function(models, options) {
      // 回收被移除成員的容器
      var removed = [];

      // 遍歷移除條件對象 models
      for (var i = 0; i < models.length; i++) {
        // 查找待移除成員
        var model = this.get(models[i]);
        // 如未找到則進行下一輪循環
        if (!model) continue;

        // 查找待移除成員的位置,並從 this.models 中將其移除
        var index = this.indexOf(model);
        this.models.splice(index, 1);
        // 將 collection 的 length 屬性減一
        this.length--;

        // 若是 remove 操做非靜默,則觸發 remove 事件。
        // 在 options 中記錄被移除成員的位置。
        if (!options.silent) {
          options.index = index;
          model.trigger('remove', model, this, options);
        }

        // 在回收容器中保存被移除成員
        removed.push(model);
        // 銷燬被移除成員與 collection 之間的引用關係
        this._removeReference(model, options);
      }
      // 返回 false 表示沒有任何成員被移除,不然返回全部被移除成員的集合
      return removed.length ? removed : false;
    },

    // Method for checking whether an object should be considered a model for
    // the purposes of adding to the collection.
    // 
    // 私有方法,檢查 model 是不是 Backbone.Model 實例
    _isModel: function (model) {
      return model instanceof Model;
    },

    // Internal method to create a model's ties to a collection.
    // 私有方法,添加成員與集合之間的引用關係。
    _addReference: function(model, options) {
      // 在 this._byId 中添加成員映射關係
      // 首先使用成員的 cid 添加映射,
      // 而後經過 modelId 對成員求值,添加映射關係。
      // 也就是說,一般 collection 會保存對成員的兩個引用關係,
      // 一個是經過 cid,另外一個是經過 idAttribute。
      this._byId[model.cid] = model;
      var id = this.modelId(model.attributes);
      if (id != null) this._byId[id] = model;
      // 爲 model 添加 all 事件回調。
      // 這裏是經過成員的 on 方法添加回調,而不是 listenTo 成員,
      // 所以若是成員執行 model.off('all'),那麼成員的任何事件都不會再轉發到 collection。
      // 很難說此處使用 listenTo 或 on 的優劣,但使用 on,則將事件主動權交到了成員手中。
      model.on('all', this._onModelEvent, this);
    },

    // Internal method to sever a model's ties to a collection.
    // 
    // 私有方法,用以銷燬成員與集合之間的引用關係。
    _removeReference: function(model, options) {
      // 依次刪除使用 cid 與 idAttribute 對成員進行引用的關係
      delete this._byId[model.cid];
      var id = this.modelId(model.attributes);
      if (id != null) delete this._byId[id];
      // 銷燬 model 的 collection 屬性
      if (this === model.collection) delete model.collection;
      // 從 model 的 all 事件回調隊列中,移除與本 collection 相關的回調函數。
      model.off('all', this._onModelEvent, this);
    },

    // Internal method called every time a model in the set fires an event.
    // Sets need to update their indexes when models change ids. All other
    // events simply proxy through. "add" and "remove" events that originate
    // in other collections are ignored.
    // 
    // 響應成員的 all 事件
    _onModelEvent: function(event, model, collection, options) {
      // add 和 remove 是來自 collection 自身,所以再也不轉發該兩個事件。
      if ((event === 'add' || event === 'remove') && collection !== this) return;
      // 當成員發生 destroy 事件時,從 collection 移除該成員。
      if (event === 'destroy') this.remove(model, options);
      if (event === 'change') {
        // 當成員發生 change 事件時,意味着成員的 id 屬性可能發生變化,
        // 因此須要在 collection 中從新檢視成員的 idAttribute 引用關係。
        var prevId = this.modelId(model.previousAttributes());
        var id = this.modelId(model.attributes);
        if (prevId !== id) {
          if (prevId != null) delete this._byId[prevId];
          if (id != null) this._byId[id] = model;
        }
      }
      // 轉發成員事件
      this.trigger.apply(this, arguments);
    }

  });

  // Underscore methods that we want to implement on the Collection.
  // 90% of the core usefulness of Backbone Collections is actually implemented
  // right here:
  var collectionMethods = { forEach: 3, each: 3, map: 3, collect: 3, reduce: 4,
      foldl: 4, inject: 4, reduceRight: 4, foldr: 4, find: 3, detect: 3, filter: 3,
      select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
      contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
      head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
      without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
      isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
      sortBy: 3, indexBy: 3};

  // Mix in each Underscore method as a proxy to `Collection#models`.
  addUnderscoreMethods(Collection, collectionMethods, 'models');

  // Backbone.View
  // -------------

  // Backbone Views are almost more convention than they are actual code. A View
  // is simply a JavaScript object that represents a logical chunk of UI in the
  // DOM. This might be a single item, an entire list, a sidebar or panel, or
  // even the surrounding frame which wraps your whole app. Defining a chunk of
  // UI as a **View** allows you to define your DOM events declaratively, without
  // having to worry about render order ... and makes it easy for the view to
  // react to specific changes in the state of your models.

  // Creating a Backbone.View creates its initial element outside of the DOM,
  // if an existing element is not provided...
  // 
  // 視圖構造函數
  var View = Backbone.View = function(options) {
    // 生成惟一標識
    this.cid = _.uniqueId('view');
    // 綁定實例屬性
    _.extend(this, _.pick(options, viewOptions));
    // 建立根節點
    this._ensureElement();
    this.initialize.apply(this, arguments);
  };

  // Cached regex to split keys for `delegate`.
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;

  // List of view options to be set as properties.
  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

  // Set up all inheritable **Backbone.View** properties and methods.
  _.extend(View.prototype, Events, {

    // The default `tagName` of a View's element is `"div"`.
    tagName: 'div',

    // jQuery delegate for element lookup, scoped to DOM elements within the
    // current view. This should be preferred to global lookups where possible.
    // 查詢本視圖做用域中的元素
    $: function(selector) {
      return this.$el.find(selector);
    },

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function(){},

    // **render** is the core function that your view should override, in order
    // to populate its element (`this.el`), with the appropriate HTML. The
    // convention is for **render** to always return `this`.
    render: function() {
      return this;
    },

    // Remove this view by taking the element out of the DOM, and removing any
    // applicable Backbone.Events listeners.
    // 移除根節點,銷燬全部監聽事件。
    remove: function() {
      this._removeElement();
      this.stopListening();
      return this;
    },

    // Remove this view's element from the document and all event listeners
    // attached to it. Exposed for subclasses using an alternative DOM
    // manipulation API.
    // 私有方法,移除根節點。
    _removeElement: function() {
      this.$el.remove();
    },

    // Change the view's element (`this.el` property) and re-delegate the
    // view's events on the new element.
    // 設置根節點元素。包括解綁以前節點委託事件,更換根節點,從新委託事件。
    setElement: function(element) {
      this.undelegateEvents();
      this._setElement(element);
      this.delegateEvents();
      return this;
    },

    // Creates the `this.el` and `this.$el` references for this view using the
    // given `el`. `el` can be a CSS selector or an HTML string, a jQuery
    // context or an element. Subclasses can override this to utilize an
    // alternative DOM manipulation API and are only required to set the
    // `this.el` property.
    // 私有方法,設置根節點。
    // 參數 el 能夠是一個 DocumentElement,或者是一個 jQuery 實例。
    _setElement: function(el) {
      this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
      this.el = this.$el[0];
    },

    // Set callbacks, where `this.events` is a hash of
    //
    // *{"event selector": "callback"}*
    //
    //     {
    //       'mousedown .title':  'edit',
    //       'click .button':     'save',
    //       'click .open':       function(e) { ... }
    //     }
    //
    // pairs. Callbacks will be bound to the view, with `this` set properly.
    // Uses event delegation for efficiency.
    // Omitting the selector binds the event to `this.el`.
    // 委託根節點事件,缺省使用 this.events 做爲委託事件。
    delegateEvents: function(events) {
      events || (events = _.result(this, 'events'));
      if (!events) return this;
      this.undelegateEvents();
      for (var key in events) {
        var method = events[key];
        if (!_.isFunction(method)) method = this[method];
        if (!method) continue;
        var match = key.match(delegateEventSplitter);
        this.delegate(match[1], match[2], _.bind(method, this));
      }
      return this;
    },

    // Add a single event listener to the view's element (or a child element
    // using `selector`). This only works for delegate-able events: not `focus`,
    // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
    // 委託事件給視圖根節點
    delegate: function(eventName, selector, listener) {
      this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
      return this;
    },

    // Clears all callbacks previously bound to the view by `delegateEvents`.
    // You usually don't need to use this, but may wish to if you have multiple
    // Backbone views attached to the same DOM element.
    // 清空根節點全部委託事件
    undelegateEvents: function() {
      if (this.$el) this.$el.off('.delegateEvents' + this.cid);
      return this;
    },

    // A finer-grained `undelegateEvents` for removing a single delegated event.
    // `selector` and `listener` are both optional.
    // 利用 jQuery 清除委託事件
    undelegate: function(eventName, selector, listener) {
      this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
      return this;
    },

    // Produces a DOM element to be assigned to your view. Exposed for
    // subclasses using an alternative DOM manipulation API.
    // 建立 DOM 元素(做爲根節點使用)
    _createElement: function(tagName) {
      return document.createElement(tagName);
    },

    // Ensure that the View has a DOM element to render into.
    // If `this.el` is a string, pass it through `$()`, take the first
    // matching element, and re-assign it to `el`. Otherwise, create
    // an element from the `id`, `className` and `tagName` properties.
    // 
    // 建立根節點。
    _ensureElement: function() {
      // 若是沒有給定根節點,則自動生成一個根節點。
      if (!this.el) {
        var attrs = _.extend({}, _.result(this, 'attributes'));
        // 添加根節點 ID
        if (this.id) attrs.id = _.result(this, 'id');
        // 添加根節點類
        if (this.className) attrs['class'] = _.result(this, 'className');
        // 建立視圖根節點
        this.setElement(this._createElement(_.result(this, 'tagName')));
        // 設置根節點 CSS 屬性
        this._setAttributes(attrs);
      } else {
        // 使用給定的根節點(Element 或 jQuery 實例)建立視圖根節點。
        this.setElement(_.result(this, 'el'));
      }
    },

    // Set attributes from a hash on this view's element.  Exposed for
    // subclasses using an alternative DOM manipulation API.
    // 設置根節點 CSS 屬性
    _setAttributes: function(attributes) {
      this.$el.attr(attributes);
    }

  });

  // Backbone.sync
  // -------------

  // Override this function to change the manner in which Backbone persists
  // models to the server. You will be passed the type of request, and the
  // model in question. By default, makes a RESTful Ajax request
  // to the model's `url()`. Some possible customizations could be:
  //
  // * Use `setTimeout` to batch rapid-fire updates into a single request.
  // * Send up the models as XML instead of JSON.
  // * Persist models via WebSockets instead of Ajax.
  //
  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
  // as `POST`, with a `_method` parameter containing the true HTTP method,
  // as well as all requests with the body as `application/x-www-form-urlencoded`
  // instead of `application/json` with the model in a param named `model`.
  // Useful when interfacing with server-side languages like **PHP** that make
  // it difficult to read the body of `PUT` requests.
  // 
  // 若是啓用 `Backbone.emulatedHTTP` ,那麼 Backbone 會將 `PUT` 和 `DELETE` 請求改成 `POST` 請求,
  // 同時增長一個 `_method` 參數用以記錄本來的請求方法。 
  Backbone.sync = function(method, model, options) {


    // sync 函數參數 method 取值範圍爲:create, read, update, delete, patch;
    // 分別映射到 HTTP 請求方法:POST, GET, PUT, DELETE, PATCH
    // 這裏是將 sync 的 method 轉換爲 HTTP 請求方法名。
    var type = methodMap[method];

    // Default options, unless specified.
    _.defaults(options || (options = {}), {
      emulateHTTP: Backbone.emulateHTTP,
      emulateJSON: Backbone.emulateJSON
    });

    // 默認請求 JSON 數據。
    // 局部變量 params 表示最後 ajax 請求參數
    var params = {type: type, dataType: 'json'};

    // 檢查是否輸入 URL 或者 model 是否自帶 URL
    if (!options.url) {
      params.url = _.result(model, 'url') || urlError();
    }

    // Ensure that we have the appropriate request data.
    // 若是 options 未給定 data 字段(即 model.fetch(options) 中的 options)
    // 而且同步的方法是寫操做,那麼默認的 xhr 請求中 contentType 應爲 json。
    // 提交的 data 優先從 options.attrs 讀取,其次讀取 model.toJSON()。
    if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
      params.contentType = 'application/json';
      params.data = JSON.stringify(options.attrs || model.toJSON(options));
    }

    // 若是設置了 Backbone.emulateJSON 爲真,則使用 application/x-www-form-urlencoded 格式提交數據。
    // 注意:
    //  這裏並非將 data 直接編碼成 HTML-form 格式,而是將整個 data 封裝在 model 字段中提交。
    //  若是不這樣作,當 model 爲 collection 時,實際的 data 是一個數組,不適宜做爲 form 提交。
    if (options.emulateJSON) {
      params.contentType = 'application/x-www-form-urlencoded';
      params.data = params.data ? {model: params.data} : {};
    }

    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
    // And an `X-HTTP-Method-Override` header.
    // 若是設置 Backbone.emulateHTTP 爲真,且 sync 爲寫操做,
    // 則統一使用 POST 方法請求,而且將原始請求方法保存在 data._method 字段中。
    // 同時增長 xhr 請求頭 X-HTTP-Method-Override。
    if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
      params.type = 'POST';
      if (options.emulateJSON) params.data._method = type;
      var beforeSend = options.beforeSend;
      options.beforeSend = function(xhr) {
        xhr.setRequestHeader('X-HTTP-Method-Override', type);
        if (beforeSend) return beforeSend.apply(this, arguments);
      };
    }

    // Don't process data on a non-GET request.
    // jQeury 的 ajax 方法,若是提交的 data 爲非字符串對象,會被默認轉換爲 query string。
    // 以匹配默認的 application/x-www-form-urlencode 類型文檔。
    // 所以對於非 GET 且未要求 emulateJSON 的請求,設置 processData 爲否以阻止 jQuery 這一默認行爲。
    if (params.type !== 'GET' && !options.emulateJSON) {
      params.processData = false;
    }

    // 從新封裝 options 中的 error,將 textStatus 和 errorThrown 記錄到 options 中。
    var error = options.error;
    options.error = function(xhr, textStatus, errorThrown) {
      options.textStatus = textStatus;
      options.errorThrown = errorThrown;
      if (error) error.call(options.context, xhr, textStatus, errorThrown);
    };

    // 使用 Backbone.ajax 發起 xhr 請求,而且將 xhr 保存在 options 中。
    var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
    // 發起請求後當即出發 request 事件,告知第三方已發起了一次 xhr 請求。
    model.trigger('request', model, xhr, options);
    return xhr;
  };

  // 默認的 Backbone.sync 中 method 與 http 請求的映射關係。
  // 爲何要自定義一套 Backbone.sync 的方法名而不直接使用 HTTP 請求方法名?
  // 由於這樣能夠將 Backbone 的同步操做與 HTTP 請求分離開,
  // 由於你也能夠經過其餘渠道來實現數據同步,例如經過改寫 sync 方法來與 local storage 同步數據。
  var methodMap = {
    'create': 'POST',
    'update': 'PUT',
    'patch':  'PATCH',
    'delete': 'DELETE',
    'read':   'GET'
  };

  // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
  // Override this if you'd like to use a different library.
  // 默認使用 jQuery.ajax 方法實現數據同步,若是使用其餘庫,能夠改寫此同步方法。
  Backbone.ajax = function() {
    return Backbone.$.ajax.apply(Backbone.$, arguments);
  };

  // Backbone.Router - 路由
  // -------------------------

  // Routers map faux-URLs to actions, and fire events when routes are
  // matched. Creating a new one sets its `routes` hash, if not set statically.
  // 
  // Router 構造函數
  var Router = Backbone.Router = function(options) {
    options || (options = {});
    if (options.routes) this.routes = options.routes;
    this._bindRoutes();
    this.initialize.apply(this, arguments);
  };

  // Cached regular expressions for matching named param parts and splatted
  // parts of route strings.
  var optionalParam = /\((.*?)\)/g;
  var namedParam    = /(\(\?)?:\w+/g;
  var splatParam    = /\*\w+/g;
  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;

  // Set up all inheritable **Backbone.Router** properties and methods.
  _.extend(Router.prototype, Events, {

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function(){},

    // Manually bind a single named route to a callback. For example:
    //
    //     this.route('search/:query/p:num', 'search', function(query, num) {
    //       ...
    //     });
    //
    // 手動添加路由
    // @param route: 字符串或正則表達式,表示路由路徑。
    // @param name: 路由名稱,表示路由器處理路由的方法(this[name]),或者 name 就是響應函數(至關於 callback)。
    // @param callback: 若是沒有給定 callback,則使用 this[name],不然使用 callback 做爲路由響應函數。 
    route: function(route, name, callback) {
      // 若是 route 不是正則表達式,則將其轉換爲正則表達式。
      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
      if (_.isFunction(name)) {
        callback = name;
        name = '';
      }
      if (!callback) callback = this[name];
      var router = this;
      // 在 Backbone.history 中添加路由(正則表達式)
      Backbone.history.route(route, function(fragment) {
        var args = router._extractParameters(route, fragment);
        // 若是 router.execute 方法返回 false,則不觸發任何事件。
        // 默認 router.execute 方法返回值固定爲 void 0,所以必定會觸發事件。
        // 若是要阻止觸發事件,只能是重寫 router.execute 方法。
        if (router.execute(callback, args, name) !== false) {
          // 是的,若是 route 第二個參數爲函數,那麼 name 就是空字符串。
          // 所以觸發的事件是 'route:'。
          // router 觸發了兩個看似相同的事件,一個是 `route:name`,另外一個是 `router`。
          router.trigger.apply(router, ['route:' + name].concat(args));
          router.trigger('route', name, args);
          Backbone.history.trigger('route', router, name, args);
        }
      });
      return this;
    },

    // Execute a route handler with the provided parameters.  This is an
    // excellent place to do pre-route setup or post-route cleanup.
    // 執行路由回調函數。
    execute: function(callback, args, name) {
      if (callback) callback.apply(this, args);
    },

    // Simple proxy to `Backbone.history` to save a fragment into the history.
    navigate: function(fragment, options) {
      Backbone.history.navigate(fragment, options);
      return this;
    },

    // Bind all defined routes to `Backbone.history`. We have to reverse the
    // order of the routes here to support behavior where the most general
    // routes can be defined at the bottom of the route map.
    // 將全部路由綁定到 `Backbone.history`。
    _bindRoutes: function() {
      // 若是未定義路由,則終止綁定操做。
      if (!this.routes) return;
      // 對 this.routes 求值。
      this.routes = _.result(this, 'routes');
      var route, routes = _.keys(this.routes);
      // 遍歷 this.routes,逐一添加路由
      while ((route = routes.pop()) != null) {
        this.route(route, this.routes[route]);
      }
    },

    // Convert a route string into a regular expression, suitable for matching
    // against the current location hash.
    _routeToRegExp: function(route) {
      route = route.replace(escapeRegExp, '\\$&')
                   .replace(optionalParam, '(?:$1)?')
                   .replace(namedParam, function(match, optional) {
                     return optional ? match : '([^/?]+)';
                   })
                   .replace(splatParam, '([^?]*?)');
      return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
    },

    // Given a route, and a URL fragment that it matches, return the array of
    // extracted decoded parameters. Empty or unmatched parameters will be
    // treated as `null` to normalize cross-browser behavior.
    // 
    // 從路由路徑中提取參數。
    // @param route: 路由正則表達式
    // @param fragment: 被 Backbone.History 確認匹配的 URL 路徑。
    _extractParameters: function(route, fragment) {
      var params = route.exec(fragment).slice(1);
      return _.map(params, function(param, i) {
        // Don't decode the search params.
        if (i === params.length - 1) return param || null;
        return param ? decodeURIComponent(param) : null;
      });
    }

  });

  // Backbone.History
  // ----------------

  // Handles cross-browser history management, based on either
  // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
  // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
  // and URL fragments. If the browser supports neither (old IE, natch),
  // falls back to polling.
  // 
  // 使用 HTML5 History API 或者 onhashchange 事件實現歷史記錄操做。
  var History = Backbone.History = function() {
    this.handlers = [];
    this.checkUrl = _.bind(this.checkUrl, this);

    // Ensure that `History` can be used outside of the browser.
    if (typeof window !== 'undefined') {
      this.location = window.location;
      this.history = window.history;
    }
  };

  // Cached regex for stripping a leading hash/slash and trailing space.
  // 正則表達式:用以刪除字符串頭部的 `#` 或 `/` 字符,以及尾部的空白。
  // 例如:'/abc/     '.replace(routeStripper, '') 獲得 'abc/'
  var routeStripper = /^[#\/]|\s+$/g;

  // Cached regex for stripping leading and trailing slashes.
  // 正則表達式:刪除字符串頭尾的 `/` 字符(確保字符串不以 `/` 開頭或結尾)
  var rootStripper = /^\/+|\/+$/g;

  // Cached regex for stripping urls of hash.
  // 正則表達式:刪除字符串中 '#' 字符(包含井字符)後全部字符。
  var pathStripper = /#.*$/;

  // Has the history handling already been started?
  History.started = false;

  // Set up all inheritable **Backbone.History** properties and methods.
  _.extend(History.prototype, Events, {

    // The default interval to poll for hash changes, if necessary, is
    // twenty times a second.
    interval: 50,

    // Are we at the app root?
    atRoot: function() {
      var path = this.location.pathname.replace(/[^\/]$/, '$&/');
      return path === this.root && !this.getSearch();
    },

    // Does the pathname match the root?
    matchRoot: function() {
      var path = this.decodeFragment(this.location.pathname);
      var root = path.slice(0, this.root.length - 1) + '/';
      return root === this.root;
    },

    // Unicode characters in `location.pathname` are percent encoded so they're
    // decoded for comparison. `%25` should not be decoded since it may be part
    // of an encoded parameter.
    // 將 fragment 從百分號編碼解碼成 UNICODE,但不解碼 `%25`,由於它有多是被編碼的參數。
    decodeFragment: function(fragment) {
      return decodeURI(fragment.replace(/%25/g, '%2525'));
    },

    // In IE6, the hash fragment and search params are incorrect if the
    // fragment contains `?`.
    getSearch: function() {
      var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
      return match ? match[0] : '';
    },

    // Gets the true hash value. Cannot use location.hash directly due to bug
    // in Firefox where location.hash will always be decoded.
    getHash: function(window) {
      var match = (window || this).location.href.match(/#(.*)$/);
      return match ? match[1] : '';
    },

    // Get the pathname and search params, without the root.
    getPath: function() {
      var path = this.decodeFragment(
        this.location.pathname + this.getSearch()
      ).slice(this.root.length - 1);
      return path.charAt(0) === '/' ? path.slice(1) : path;
    },

    // Get the cross-browser normalized URL fragment from the path or hash.
    getFragment: function(fragment) {
      if (fragment == null) {
        if (this._usePushState || !this._wantsHashChange) {
          fragment = this.getPath();
        } else {
          fragment = this.getHash();
        }
      }
      return fragment.replace(routeStripper, '');
    },

    // Start the hash change handling, returning `true` if the current URL matches
    // an existing route, and `false` otherwise.
    // 啓動 History 路由,若是當前 URL 匹配到了某條路由,返回 true,不然返回 false。
    start: function(options) {
      // History 是個單例應用,不容許重複啓動。
      if (History.started) throw new Error('Backbone.history has already been started');
      History.started = true;

      // Figure out the initial configuration. Do we need an iframe?
      // Is pushState desired ... is it available?
      this.options          = _.extend({root: '/'}, this.options, options);
      this.root             = this.options.root;
      this._wantsHashChange = this.options.hashChange !== false;
      this._hasHashChange   = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
      this._useHashChange   = this._wantsHashChange && this._hasHashChange;
      this._wantsPushState  = !!this.options.pushState;
      this._hasPushState    = !!(this.history && this.history.pushState);
      this._usePushState    = this._wantsPushState && this._hasPushState;
      this.fragment         = this.getFragment();

      // Normalize root to always include a leading and trailing slash.
      this.root = ('/' + this.root + '/').replace(rootStripper, '/');

      // Transition from hashChange to pushState or vice versa if both are
      // requested.
      if (this._wantsHashChange && this._wantsPushState) {

        // If we've started off with a route from a `pushState`-enabled
        // browser, but we're currently in a browser that doesn't support it...
        if (!this._hasPushState && !this.atRoot()) {
          var root = this.root.slice(0, -1) || '/';
          this.location.replace(root + '#' + this.getPath());
          // Return immediately as browser will do redirect to new url
          return true;

        // Or if we've started out with a hash-based route, but we're currently
        // in a browser where it could be `pushState`-based instead...
        } else if (this._hasPushState && this.atRoot()) {
          this.navigate(this.getHash(), {replace: true});
        }

      }

      // Proxy an iframe to handle location events if the browser doesn't
      // support the `hashchange` event, HTML5 history, or the user wants
      // `hashChange` but not `pushState`.
      if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
        this.iframe = document.createElement('iframe');
        this.iframe.src = 'javascript:0';
        this.iframe.style.display = 'none';
        this.iframe.tabIndex = -1;
        var body = document.body;
        // Using `appendChild` will throw on IE < 9 if the document is not ready.
        var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
        iWindow.document.open();
        iWindow.document.close();
        iWindow.location.hash = '#' + this.fragment;
      }

      // Add a cross-platform `addEventListener` shim for older browsers.
      var addEventListener = window.addEventListener || function (eventName, listener) {
        return attachEvent('on' + eventName, listener);
      };

      // Depending on whether we're using pushState or hashes, and whether
      // 'onhashchange' is supported, determine how we check the URL state.
      if (this._usePushState) {
        addEventListener('popstate', this.checkUrl, false);
      } else if (this._useHashChange && !this.iframe) {
        addEventListener('hashchange', this.checkUrl, false);
      } else if (this._wantsHashChange) {
        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
      }

      if (!this.options.silent) return this.loadUrl();
    },

    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
    // but possibly useful for unit testing Routers.
    stop: function() {
      // Add a cross-platform `removeEventListener` shim for older browsers.
      var removeEventListener = window.removeEventListener || function (eventName, listener) {
        return detachEvent('on' + eventName, listener);
      };

      // Remove window listeners.
      if (this._usePushState) {
        removeEventListener('popstate', this.checkUrl, false);
      } else if (this._useHashChange && !this.iframe) {
        removeEventListener('hashchange', this.checkUrl, false);
      }

      // Clean up the iframe if necessary.
      if (this.iframe) {
        document.body.removeChild(this.iframe);
        this.iframe = null;
      }

      // Some environments will throw when clearing an undefined interval.
      if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
      History.started = false;
    },

    // Add a route to be tested when the fragment changes. Routes added later
    // may override previous routes.
    route: function(route, callback) {
      this.handlers.unshift({route: route, callback: callback});
    },

    // Checks the current URL to see if it has changed, and if it has,
    // calls `loadUrl`, normalizing across the hidden iframe.
    checkUrl: function(e) {
      var current = this.getFragment();

      // If the user pressed the back button, the iframe's hash will have
      // changed and we should use that for comparison.
      if (current === this.fragment && this.iframe) {
        current = this.getHash(this.iframe.contentWindow);
      }

      if (current === this.fragment) return false;
      if (this.iframe) this.navigate(current);
      this.loadUrl();
    },

    // Attempt to load the current URL fragment. If a route succeeds with a
    // match, returns `true`. If no defined routes matches the fragment,
    // returns `false`.
    loadUrl: function(fragment) {
      // If the root doesn't match, no routes can match either.
      if (!this.matchRoot()) return false;
      fragment = this.fragment = this.getFragment(fragment);
      return _.some(this.handlers, function(handler) {
        if (handler.route.test(fragment)) {
          handler.callback(fragment);
          return true;
        }
      });
    },

    // Save a fragment into the hash history, or replace the URL state if the
    // 'replace' option is passed. You are responsible for properly URL-encoding
    // the fragment in advance.
    //
    // The options object can contain `trigger: true` if you wish to have the
    // route callback be fired (not usually desirable), or `replace: true`, if
    // you wish to modify the current URL without adding an entry to the history.
    navigate: function(fragment, options) {
      if (!History.started) return false;
      if (!options || options === true) options = {trigger: !!options};

      // Normalize the fragment.
      fragment = this.getFragment(fragment || '');

      // Don't include a trailing slash on the root.
      var root = this.root;
      if (fragment === '' || fragment.charAt(0) === '?') {
        root = root.slice(0, -1) || '/';
      }
      var url = root + fragment;

      // Strip the hash and decode for matching.
      fragment = this.decodeFragment(fragment.replace(pathStripper, ''));

      if (this.fragment === fragment) return;
      this.fragment = fragment;

      // If pushState is available, we use it to set the fragment as a real URL.
      if (this._usePushState) {
        this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);

      // If hash changes haven't been explicitly disabled, update the hash
      // fragment to store history.
      } else if (this._wantsHashChange) {
        this._updateHash(this.location, fragment, options.replace);
        if (this.iframe && (fragment !== this.getHash(this.iframe.contentWindow))) {
          var iWindow = this.iframe.contentWindow;

          // Opening and closing the iframe tricks IE7 and earlier to push a
          // history entry on hash-tag change.  When replace is true, we don't
          // want this.
          if (!options.replace) {
            iWindow.document.open();
            iWindow.document.close();
          }

          this._updateHash(iWindow.location, fragment, options.replace);
        }

      // If you've told us that you explicitly don't want fallback hashchange-
      // based history, then `navigate` becomes a page refresh.
      } else {
        return this.location.assign(url);
      }
      if (options.trigger) return this.loadUrl(fragment);
    },

    // Update the hash location, either replacing the current entry, or adding
    // a new one to the browser history.
    _updateHash: function(location, fragment, replace) {
      if (replace) {
        var href = location.href.replace(/(javascript:|#).*$/, '');
        location.replace(href + '#' + fragment);
      } else {
        // Some browsers require that `hash` contains a leading #.
        location.hash = '#' + fragment;
      }
    }

  });

  // Create the default Backbone.history.
  Backbone.history = new History;

  // Helpers
  // -------

  // Helper function to correctly set up the prototype chain for subclasses.
  // Similar to `goog.inherits`, but uses a hash of prototype properties and
  // class properties to be extended.
  // `extend` 函數經過設置子類的原型鏈實現繼承機制,它能夠同時擴展父類的原型
  // 屬性和類屬性。
  var extend = function(protoProps, staticProps) {
    var parent = this;  // 上下文應指向父類
    var child; // 子類(構造函數)

    // 當傳入原型對象包含 `constructor` 屬性,則直接做爲子類的構造函數。
    // 不然新建一個構造函數,並在構造函數中調用父類構造函數。
    if (protoProps && _.has(protoProps, 'constructor')) {
      child = protoProps.constructor;
    } else {
      child = function(){ return parent.apply(this, arguments); };
    }

    // 將父類靜態屬性和新傳入的靜態屬性擴展到子類上。
    _.extend(child, parent, staticProps);

    // 設置中間人(或者代理構造函數),將中間人 constructor 屬性設置爲子類構造函數。
    // 將子類的 prototype 設置爲中間人實例,從而使得子類處於中間人原型鏈上。
    // 避免將子類 prototype 直接指向中間人的 prototype,可使得對父類 prototype 的修改,
    // 直接做用到子類上,但對子類 prototype 的修改,會被父類實例隔絕,從而避免做用到父類身上。
    // 
    // 使用中間人鏈接 child 和 parent,不將 child 的 prototype 直接指向 parent 的 prototype。
    // 緣由在於子類 prototype 應指向父類實例,從而避免原型鏈上的逆向做用。
    // 使用中間人,將中間人的 prototype 指向 parent 的 prototype,
    // 能夠保證明現繼承同時避免調用 parent 的構造函數,從而帶來反作用。
    var Surrogate = function(){ this.constructor = child; };
    Surrogate.prototype = parent.prototype;
    child.prototype = new Surrogate;

    // 擴展子類的原型(實例方法)
    if (protoProps) _.extend(child.prototype, protoProps);

    // 添加 __super__ 屬性指向父類的原型,以便在子類中能夠調用父類原型。
    child.__super__ = parent.prototype;

    return child;
  };

  // Set up inheritance for the model, collection, router, view and history.
  Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;

  // Throw an error when a URL is needed, and none is supplied.
  // 異常:URL 不存在
  var urlError = function() {
    throw new Error('A "url" property or function must be specified');
  };

  // Wrap an optional error callback with a fallback error event.
  // 封裝 error 回調(在 model.fetch 方法中,ajax 的 error 回調)
  // 保證不管是否存在 options.error 回調,都會觸發 model 的 error 事件。
  var wrapError = function(model, options) {
    var error = options.error;
    options.error = function(resp) {
      if (error) error.call(options.context, model, resp, options);
      model.trigger('error', model, resp, options);
    };
  };

  return Backbone;

}));
相關文章
相關標籤/搜索