1. 程式人生 > >Backbone.js原始碼分析(珍藏版)

Backbone.js原始碼分析(珍藏版)

// Backbone.js 0.9.2


// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function () {


    // 建立一個全域性物件, 在瀏覽器中表示為window物件, 在Node.js中表示global物件
    var root = this;


    // 儲存"Backbone"變數被覆蓋之前的值
    // 如果出現命名衝突或考慮到規範, 可通過Backbone.noConflict()方法恢復該變數被Backbone佔用之前的值, 並返回Backbone物件以便重新命名
    var previousBackbone = root.Backbone;


    // 將Array.prototype中的slice和splice方法快取到區域性變數以供呼叫
    var slice = Array.prototype.slice;
    var splice = Array.prototype.splice;


    var Backbone;
    if (typeof exports !== 'undefined') {
        Backbone = exports;
    } else {
        Backbone = root.Backbone = {};
    }


    // 定義Backbone版本
    Backbone.VERSION = '0.9.2';


    // 在伺服器環境下自動匯入Underscore, 在Backbone中部分方法依賴或繼承自Underscore
    var _ = root._;
    if (!_ && (typeof require !== 'undefined'))
        _ = require('underscore');


    // 定義第三方庫為統一的變數"$", 用於在檢視(View), 事件處理和與伺服器資料同步(sync)時呼叫庫中的方法
    // 支援的庫包括jQuery, Zepto等, 它們語法相同, 但Zepto更適用移動開發, 它主要針對Webkit核心瀏覽器
    // 也可以通過自定義一個與jQuery語法相似的自定義庫, 供Backbone使用(有時我們可能需要一個比jQuery, Zepto更輕巧的自定義版本)
    // 這裡定義的"$"是區域性變數, 因此不會影響在Backbone框架之外第三方庫的正常使用
    var $ = root.jQuery || root.Zepto || root.ender;


    // 手動設定第三方庫
    // 如果在匯入了Backbone之前並沒有匯入第三方庫, 可以通過setDomLibrary方法設定"$"區域性變數
    // setDomLibrary方法也常用於在Backbone中動態匯入自定義庫
    Backbone.setDomLibrary = function (lib) {
        $ = lib;
    };
    // 放棄以"Backbone"命名框架, 並返回Backbone物件, 一般用於避免命名衝突或規範命名方式
    // 例如:
    // var bk = Backbone.noConflict(); // 取消"Backbone"命名, 並將Backbone物件存放於bk變數中
    // console.log(Backbone); // 該變數已經無法再訪問Backbone物件, 而恢復為Backbone定義前的值
    // var MyBackbone = bk; // 而bk儲存了Backbone物件, 我們將它重新命名為MyBackbone
    Backbone.noConflict = function () {
        root.Backbone = previousBackbone;
        return this;
    };
    // 對於不支援REST方式的瀏覽器, 可以設定Backbone.emulateHTTP = true
    // 與伺服器請求將以POST方式傳送, 並在資料中加入_method引數標識操作名稱, 同時也將傳送X-HTTP-Method-Override頭資訊
    Backbone.emulateHTTP = false;


    // 對於不支援application/json編碼的瀏覽器, 可以設定Backbone.emulateJSON = true;
    // 將請求型別設定為application/x-www-form-urlencoded, 並將資料放置在model引數中實現相容
    Backbone.emulateJSON = false;


    // Backbone.Events 自定義事件相關
    // -----------------


    // eventSplitter指定處理多個事件時, 事件名稱的解析規則
    var eventSplitter = /\s+/;


    // 自定義事件管理器
    // 通過在物件中繫結Events相關方法, 允許向物件新增, 刪除和觸發自定義事件
    var Events = Backbone.Events = {


        // 將自定義事件(events)和回撥函式(callback)繫結到當前物件
        // 回撥函式中的上下文物件為指定的context, 如果沒有設定context則上下文物件預設為當前繫結事件的物件
        // 該方法類似與DOM Level2中的addEventListener方法
        // events允許指定多個事件名稱, 通過空白字元進行分隔(如空格, 製表符等)
        // 當事件名稱為"all"時, 在呼叫trigger方法觸發任何事件時, 均會呼叫"all"事件中繫結的所有回撥函式
        on: function (events, callback, context) {
            // 定義一些函式中使用到的區域性變數
            var calls, event, node, tail, list;
            // 必須設定callback回撥函式
            if (!callback)
                return this;
            // 通過eventSplitter對事件名稱進行解析, 使用split將多個事件名拆分為一個數組
            // 一般使用空白字元指定多個事件名稱
            events = events.split(eventSplitter);
            // calls記錄了當前物件中已繫結的事件與回撥函式列表
            calls = this._callbacks || (this._callbacks = {});


            // 迴圈事件名列表, 從頭至尾依次將事件名存放至event變數
            while (event = events.shift()) {
                // 獲取已經繫結event事件的回撥函式
                // list儲存單個事件名中繫結的callback回撥函式列表
                // 函式列表並沒有通過陣列方式儲存, 而是通過多個物件的next屬性進行依次關聯
                /** 資料格式如:
                 * {
                 *     tail: {Object},
                 *     next: {
                 *         callback: {Function},
                 *         context: {Object},
                 *         next: {
                 *             callback: {Function},
                 *             context: {Object},
                 *             next: {Object}
                 *         }
                 *     }
                 * }
                 */
                // 列表每一層next物件儲存了一次回撥事件相關資訊(函式體, 上下文和下一次回撥事件)
                // 事件列表最頂層儲存了一個tail物件, 它儲存了最後一次繫結回撥事件的標識(與最後一次回撥事件的next指向同一個物件)
                // 通過tail標識, 可以在遍歷回撥列表時得知已經到達最後一個回撥函式
                list = calls[event];
                // node變數用於記錄本次回撥函式的相關資訊
                // tail只儲存最後一次繫結回撥函式的標識
                // 因此如果之前已經繫結過回撥函式, 則將之前的tail指定給node作為一個物件使用, 然後建立一個新的物件標識給tail
                // 這裡之所以要將本次回撥事件新增到上一次回撥的tail物件, 是為了讓回撥函式列表的物件層次關係按照繫結順序排列(最新繫結的事件將被放到最底層)
                node = list ? list.tail : {};
                node.next = tail = {};
                // 記錄本次回撥的函式體及上下文資訊
                node.context = context;
                node.callback = callback;
                // 重新組裝當前事件的回撥列表, 列表中已經加入了本次回撥事件
                calls[event] = {
                    tail: tail,
                    next: list ? list.next : node
                };
            }
            // 返回當前物件, 方便進行方法鏈呼叫
            return this;
        },
        // 移除物件中已繫結的事件或回撥函式, 可以通過events, callback和context對需要刪除的事件或回撥函式進行過濾
        // - 如果context為空, 則移除所有的callback指定的函式
        // - 如果callback為空, 則移除事件中所有的回撥函式
        // - 如果events為空, 但指定了callback或context, 則移除callback或context指定的回撥函式(不區分事件名稱)
        // - 如果沒有傳遞任何引數, 則移除物件中繫結的所有事件和回撥函式
        off: function (events, callback, context) {
            var event, calls, node, tail, cb, ctx;


            // No events, or removing *all* events.
            // 當前物件沒有繫結任何事件
            if (!(calls = this._callbacks))
                return;
            // 如果沒有指定任何引數, 則移除所有事件和回撥函式(刪除_callbacks屬性)
            if (!(events || callback || context)) {
                delete this._callbacks;
                return this;
            }


            // 解析需要移除的事件列表
            // - 如果指定了events, 則按照eventSplitter對事件名進行解析
            // - 如果沒有指定events, 則解析已繫結所有事件的名稱列表
            events = events ? events.split(eventSplitter) : _.keys(calls);


            // 迴圈事件名列表
            while (event = events.shift()) {
                // 將當前事件物件從列表中移除, 並快取到node變數中
                node = calls[event];
                delete calls[event];
                // 如果不存在當前事件物件(或沒有指定移除過濾條件, 則認為將移除當前事件及所有回撥函式), 則終止此次操作(事件物件在上一步已經移除)
                if (!node || !(callback || context))
                    continue;
                // Create a new list, omitting the indicated callbacks.
                // 根據回撥函式或上下文過濾條件, 組裝一個新的事件物件並重新繫結
                tail = node.tail;
                // 遍歷事件中的所有回撥物件
                while ((node = node.next) !== tail) {
                    cb = node.callback;
                    ctx = node.context;
                    // 根據引數中的回撥函式和上下文, 對回撥函式進行過濾, 將不符合過濾條件的回撥函式重新繫結到事件中(因為事件中的所有回撥函式在上面已經被移除)
                    if ((callback && cb !== callback) || (context && ctx !== context)) {
                        this.on(event, cb, ctx);
                    }
                }
            }


            return this;
        },
        // 觸發已經定義的一個或多個事件, 依次執行繫結的回撥函式列表
        trigger: function (events) {
            var event, node, calls, tail, args, all, rest;
            // 當前物件沒有繫結任何事件
            if (!(calls = this._callbacks))
                return this;
            // 獲取回撥函式列表中繫結的"all"事件列表
            all = calls.all;
            // 將需要觸發的事件名稱, 按照eventSplitter規則解析為一個數組
            events = events.split(eventSplitter);
            // 將trigger從第2個之後的引數, 記錄到rest變數, 將依次傳遞給回撥函式
            rest = slice.call(arguments, 1);


            // 迴圈需要觸發的事件列表
            while (event = events.shift()) {
                // 此處的node變數記錄了當前事件的所有回撥函式列表
                if (node = calls[event]) {
                    // tail變數記錄最後一次繫結事件的物件標識
                    tail = node.tail;
                    // node變數的值, 按照事件的繫結順序, 被依次賦值為繫結的單個回撥事件物件
                    // 最後一次繫結的事件next屬性, 與tail引用同一個物件, 以此作為是否到達列表末尾的判斷依據
                    while ((node = node.next) !== tail) {
                        // 執行所有繫結的事件, 並將呼叫trigger時的引數傳遞給回撥函式
                        node.callback.apply(node.context || this, rest);
                    }
                }
                // 變數all記錄了繫結時的"all"事件, 即在呼叫任何事件時, "all"事件中的回撥函式均會被執行
                // - "all"事件中的回撥函式無論繫結順序如何, 都會在當前事件的回撥函式列表全部執行完畢後再依次執行
                // - "all"事件應該在觸發普通事件時被自動呼叫, 如果強制觸發"all"事件, 事件中的回撥函式將被執行兩次
                if (node = all) {
                    tail = node.tail;
                    // 與呼叫普通事件的回撥函式不同之處在於, all事件會將當前呼叫的事件名作為第一個引數傳遞給回撥函式
                    args = [event].concat(rest);
                    // 遍歷並執行"all"事件中的回撥函式列表
                    while ((node = node.next) !== tail) {
                        node.callback.apply(node.context || this, args);
                    }
                }
            }


            return this;
        }
    };


    // 繫結事件與釋放事件的別名, 也為了同時相容Backbone以前的版本
    Events.bind = Events.on;
    Events.unbind = Events.off;


    // Backbone.Model 資料物件模型
    // --------------


    // Model是Backbone中所有資料物件模型的基類, 用於建立一個數據模型
    // @param {Object} attributes 指定建立模型時的初始化資料
    // @param {Object} options
    /**
     * @format options
     * {
     *     parse: {Boolean},
     *     collection: {Collection}
     * }
     */
    var Model = Backbone.Model = function (attributes, options) {
        // defaults變數用於儲存模型的預設資料
        var defaults;
        // 如果沒有指定attributes引數, 則設定attributes為空物件
        attributes || (attributes = {});
        // 設定attributes預設資料的解析方法, 例如預設資料是從伺服器獲取(或原始資料是XML格式), 為了相容set方法所需的資料格式, 可使用parse方法進行解析
        if (options && options.parse)
            attributes = this.parse(attributes);
        if (defaults = getValue(this, 'defaults')) {
            // 如果Model在定義時設定了defaults預設資料, 則初始化資料使用defaults與attributes引數合併後的資料(attributes中的資料會覆蓋defaults中的同名數據)
            attributes = _.extend({}, defaults, attributes);
        }
        // 顯式指定模型所屬的Collection物件(在呼叫Collection的add, push等將模型新增到集合中的方法時, 會自動設定模型所屬的Collection物件)
        if (options && options.collection)
            this.collection = options.collection;
        // attributes屬性儲存了當前模型的JSON物件化資料, 建立模型時預設為空
        this.attributes = {};
        // 定義_escapedAttributes快取物件, 它將快取通過escape方法處理過的資料
        this._escapedAttributes = {};
        // 為每一個模型配置一個唯一標識
        this.cid = _.uniqueId('c');
        // 定義一系列用於記錄資料狀態的物件, 具體含義請參考物件定義時的註釋
        this.changed = {};
        this._silent = {};
        this._pending = {};
        // 建立例項時設定初始化資料, 首次設定使用silent引數, 不會觸發change事件
        this.set(attributes, {
            silent: true
        });
        // 上面已經設定了初始化資料, changed, _silent, _pending物件的狀態可能已經發生變化, 這裡重新進行初始化
        this.changed = {};
        this._silent = {};
        this._pending = {};
        // _previousAttributes變數儲存模型資料的一個副本
        // 用於在change事件中獲取模型資料被改變之前的狀態, 可通過previous或previousAttributes方法獲取上一個狀態的資料
        this._previousAttributes = _.clone(this.attributes);
        // 呼叫initialize初始化方法
        this.initialize.apply(this, arguments);
    };
    // 使用extend方法為Model原型定義一系列屬性和方法
    _.extend(Model.prototype, Events, {


        // changed屬性記錄了每次呼叫set方法時, 被改變資料的key集合
        changed: null,


        // // 當指定silent屬性時, 不會觸發change事件, 被改變的資料會記錄下來, 直到下一次觸發change事件
        // _silent屬性用來記錄使用silent時的被改變的資料
        _silent: null,


        _pending: null,


        // 每個模型的唯一標識屬性(預設為"id", 通過修改idAttribute可自定義id屬性名)
        // 如果在設定資料時包含了id屬性, 則id將會覆蓋模型的id
        // id用於在Collection集合中查詢和標識模型, 與後臺介面通訊時也會以id作為一條記錄的標識
        idAttribute: 'id',


        // 模型初始化方法, 在模型被構造結束後自動呼叫
        initialize: function () {},
        // 返回當前模型中資料的一個副本(JSON物件格式)
        toJSON: function (options) {
            return _.clone(this.attributes);
        },
        // 根據attr屬性名, 獲取模型中的資料值
        get: function (attr) {
            return this.attributes[attr];
        },
        // 根據attr屬性名, 獲取模型中的資料值, 資料值包含的HTML特殊字元將被轉換為HTML實體, 包含 & < > " ' \
        // 通過 _.escape方法實現
        escape: function (attr) {
            var html;
            // 從_escapedAttributes快取物件中查詢資料, 如果資料已經被快取則直接返回
            if (html = this._escapedAttributes[attr])
                return html;
            // _escapedAttributes快取物件中沒有找到資料
            // 則先從模型中獲取資料
            var val = this.get(attr);
            // 將資料中的HTML使用 _.escape方法轉換為實體, 並快取到_escapedAttributes物件, 便於下次直接獲取
            return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
        },
        // 檢查模型中是否存在某個屬性, 當該屬性的值被轉換為Boolean型別後值為false, 則認為不存在
        // 如果值為false, null, undefined, 0, NaN, 或空字串時, 均會被轉換為false
        has: function (attr) {
            return this.get(attr) != null;
        },
        // 設定模型中的資料, 如果key值不存在, 則作為新的屬性新增到模型, 如果key值已經存在, 則修改為新的值
        set: function (key, value, options) {
            // attrs變數中記錄需要設定的資料物件
            var attrs, attr, val;


            // 引數形式允許key-value物件形式, 或通過key, value兩個引數進行單獨設定
            // 如果key是一個物件, 則認定為使用物件形式設定, 第二個引數將被視為options引數
            if (_.isObject(key) || key == null) {
                attrs = key;
                options = value;
            } else {
                // 通過key, value兩個引數單獨設定, 將資料放到attrs物件中方便統一處理
                attrs = {};
                attrs[key] = value;
            }


            // options配置項必須是一個物件, 如果沒有設定options則預設值為一個空物件
            options || (options = {});
            // 沒有設定引數時不執行任何動作
            if (!attrs)
                return this;
            // 如果被設定的資料物件屬於Model類的一個例項, 則將Model物件的attributes資料物件賦給attrs
            // 一般在複製一個Model物件的資料到另一個Model物件時, 會執行該動作
            if (attrs instanceof Model)
                attrs = attrs.attributes;
            // 如果options配置物件中設定了unset屬性, 則將attrs資料物件中的所有屬性重置為undefined
            // 一般在複製一個Model物件的資料到另一個Model物件時, 但僅僅需要複製Model中的資料而不需要複製值時執行該操作
            if (options.unset)
                for (attr in attrs)
                    attrs[attr] =
                    void 0;


            // 對當前資料進行驗證, 如果驗證未通過則停止執行
            if (!this._validate(attrs, options))
                return false;


            // 如果設定的id屬性名被包含在資料集合中, 則將id覆蓋到模型的id屬性
            // 這是為了確保在自定義id屬性名後, 訪問模型的id屬性時, 也能正確訪問到id
            if (this.idAttribute in attrs)
                this.id = attrs[this.idAttribute];


            var changes = options.changes = {};
            // now記錄當前模型中的資料物件
            var now = this.attributes;
            // escaped記錄當前模型中通過escape快取過的資料
            var escaped = this._escapedAttributes;
            // prev記錄模型中資料被改變之前的值
            var prev = this._previousAttributes || {};


            // 遍歷需要設定的資料物件
            for (attr in attrs) {
                // attr儲存當前屬性名稱, val儲存當前屬性的值
                val = attrs[attr];


                // 如果當前資料在模型中不存在, 或已經發生變化, 或在options中指定了unset屬性刪除, 則刪除該資料被換存在_escapedAttributes中的資料
                if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
                    // 僅刪除通過escape快取過的資料, 這是為了保證快取中的資料與模型中的真實資料保持同步
                    delete escaped[attr];
                    // 如果指定了silent屬性, 則此次set方法呼叫不會觸發change事件, 因此將被改變的資料記錄到_silent屬性中, 便於下一次觸發change事件時, 通知事件監聽函式此資料已經改變
                    // 如果沒有指定silent屬性, 則直接設定changes屬性中當前資料為已改變狀態
                    (options.silent ? this._silent : changes)[attr] = true;
                }


                // 如果在options中設定了unset, 則從模型中刪除該資料(包括key)
                // 如果沒有指定unset屬性, 則認為將新增或修改資料, 向模型的資料物件中加入新的資料
                options.unset ?
                    delete now[attr] : now[attr] = val;


                // 如果模型中的資料與新的資料不一致, 則表示該資料已發生變化
                if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
                    // 在changed屬性中記錄當前屬性已經發生變化的狀態
                    this.changed[attr] = val;
                    if (!options.silent)
                        this._pending[attr] = true;
                } else {
                    // 如果資料沒有發生變化, 則從changed屬性中移除已變化狀態
                    delete this.changed[attr];
                    delete this._pending[attr];
                }
            }


            // 呼叫change方法, 將觸發change事件繫結的函式
            if (!options.silent)
                this.change(options);
            return this;
        },
        // 從當前模型中刪除指定的資料(屬性也將被同時刪除)
        unset: function (attr, options) {
            (options || (options = {})).unset = true;
            // 通過options.unset配置項告知set方法進行刪除操作
            return this.set(attr, null, options);
        },
        // 清除當前模型中的所有資料和屬性
        clear: function (options) {
            (options || (options = {})).unset = true;
            // 克隆一個當前模型的屬性副本, 並通過options.unset配置項告知set方法執行刪除操作
            return this.set(_.clone(this.attributes), options);
        },
        // 從伺服器獲取預設的模型資料, 獲取資料後使用set方法將資料填充到模型, 因此如果獲取到的資料與當前模型中的資料不一致, 將會觸發change事件
        fetch: function (options) {
            // 確保options是一個新的物件, 隨後將改變options中的屬性
            options = options ? _.clone(options) : {};
            var model = this;
            // 在options中可以指定獲取資料成功後的自定義回撥函式
            var success = options.success;
            // 當獲取資料成功後填充資料並呼叫自定義成功回撥函式
            options.success = function (resp, status, xhr) {
                // 通過parse方法將伺服器返回的資料進行轉換
                // 通過set方法將轉換後的資料填充到模型中, 因此可能會觸發change事件(當資料發生變化時)
                // 如果填充資料時驗證失敗, 則不會呼叫自定義success回撥函式
                if (!model.set(model.parse(resp, xhr), options))
                    return false;
                // 呼叫自定義的success回撥函式
                if (success)
                    success(model, resp);
            };
            // 請求發生錯誤時通過wrapError處理error事件
            options.error = Backbone.wrapError(options.error, model, options);
            // 呼叫sync方法從伺服器獲取資料
            return (this.sync || Backbone.sync).call(this, 'read', this, options);
        },
        // 儲存模型中的資料到伺服器
        save: function (key, value, options) {
            // attrs儲存需要儲存到伺服器的資料物件
            var attrs, current;


            // 支援設定單個屬性的方式 key: value
            // 支援物件形式的批量設定方式 {key: value}
            if (_.isObject(key) || key == null) {
                // 如果key是一個物件, 則認為是通過物件方式設定
                // 此時第二個引數被認為是options
                attrs = key;
                options = value;
            } else {
                // 如果是通過key: value形式設定單個屬性, 則直接設定attrs
                attrs = {};
                attrs[key] = value;
            }
            // 配置物件必須是一個新的物件
            options = options ? _.clone(options) : {};


            // 如果在options中設定了wait選項, 則被改變的資料將會被提前驗證, 且伺服器沒有響應新資料(或響應失敗)時, 本地資料會被還原為修改前的狀態
            // 如果沒有設定wait選項, 則無論伺服器是否設定成功, 本地資料均會被修改為最新狀態
            if (options.wait) {
                // 對需要儲存的資料提前進行驗證
                if (!this._validate(attrs, options))
                    return false;
                // 記錄當前模型中的資料, 用於在將資料傳送到伺服器後, 將資料進行還原
                // 如果伺服器響應失敗或沒有返回資料, 則可以保持修改前的狀態
                current = _.clone(this.attributes);
            }


            // silentOptions在options物件中加入了silent(不對資料進行驗證)
            // 當使用wait引數時使用silentOptions配置項, 因為在上面已經對資料進行過驗證
            // 如果沒有設定wait引數, 則仍然使用原始的options配置項
            var silentOptions = _.extend({}, options, {
                silent: true
            });
            // 將修改過最新的資料儲存到模型中, 便於在sync方法中獲取模型資料儲存到伺服器
            if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
                return false;
            }


            var model = this;
            // 在options中可以指定儲存資料成功後的自定義回撥函式
            var success = options.success;
            // 伺服器響應成功後執行success
            options.success = function (resp, status, xhr) {
                // 獲取伺服器響應最新狀態的資料
                var serverAttrs = model.parse(resp, xhr);
                // 如果使用了wait引數, 則優先將修改後的資料狀態直接設定到模型
                if (options.wait) {
                    delete options.wait;
                    serverAttrs = _.extend(attrs || {}, serverAttrs);
                }
                // 將最新的資料狀態設定到模型中
                // 如果呼叫set方法時驗證失敗, 則不會呼叫自定義的success回撥函式
                if (!model.set(serverAttrs, options))
                    return false;
                if (success) {
                    // 呼叫響應成功後自定義的success回撥函式
                    success(model, resp);
                } else {
                    // 如果沒有指定自定義回撥, 則預設觸發sync事件
                    model.trigger('sync', model, resp, options);
                }
            };
            // 請求發生錯誤時通過wrapError處理error事件
            options.error = Backbone.wrapError(options.error, model, options);
            // 將模型中的資料儲存到伺服器
            // 如果當前模型是一個新建的模型(沒有id), 則使用create方法(新增), 否則認為是update方法(修改)
            var method = this.isNew() ? 'create' : 'update';
            var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
            // 如果設定了options.wait, 則將資料還原為修改前的狀態
            // 此時儲存的請求還沒有得到響應, 因此如果響應失敗, 模型中將保持修改前的狀態, 如果伺服器響應成功, 則會在success中設定模型中的資料為最新狀態
            if (options.wait)
                this.set(current, silentOptions);
            return xhr;
        },
        // 刪除模型, 模型將同時從所屬的Collection集合中被刪除
        // 如果模型是在客戶端新建的, 則直接從客戶端刪除
        // 如果模型資料同時存在伺服器, 則同時會刪除伺服器端的資料
        destroy: function (options) {
            // 配置項必須是一個新的物件
            options = options ? _.clone(options) : {};
            var model = this;
            // 在options中可以指定刪除資料成功後的自定義回撥函式
            var success = options.success;
            // 刪除資料成功呼叫, 觸發destroy事件, 如果模型存在於Collection集合中, 集合將監聽destroy事件並在觸發時從集合中移除該模型
            // 刪除模型時, 模型中的資料並沒有被清空, 但模型已經從集合中移除, 因此當沒有任何地方引用該模型時, 會被自動從記憶體中釋放
            // 建議在刪除模型時, 將模型物件的引用變數設定為null
            var triggerDestroy = function () {
                model.trigger('destroy', model, model.collection, options);
            };
            // 如果該模型是一個客戶端新建的模型, 則直接呼叫triggerDestroy從集合中將模型移除
            if (this.isNew()) {
                triggerDestroy();
                return false;
            }


            // 當從伺服器刪除資料成功時
            options.success = function (resp) {
                // 如果在options物件中配置wait項, 則表示本地記憶體中的模型資料, 會在伺服器資料被刪除成功後再刪除
                // 如果伺服器響應失敗, 則本地資料不會被刪除
                if (options.wait)
                    triggerDestroy();
                if (success) {
                    // 呼叫自定義的成功回撥函式
                    success(model, resp);
                } else {
                    // 如果沒有自定義回撥, 則預設觸發sync事件
                    model.trigger('sync', model, resp, options);
                }
            };
            // 請求發生錯誤時通過wrapError處理error事件
            options.error = Backbone.wrapError(options.error, model, options);
            // 通過sync方法傳送刪除資料的請求
            var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
            // 如果沒有在options物件中配置wait項, 則會先刪除本地資料, 再發送請求刪除伺服器資料
            // 此時無論伺服器刪除是否成功, 本地模型資料已被刪除
            if (!options.wait)
                triggerDestroy();
            return xhr;
        },
        // 獲取模型在伺服器介面中對應的url, 在呼叫save, fetch, destroy等與伺服器互動的方法時, 將使用該方法獲取url
        // 生成的url類似於"PATHINFO"模式, 伺服器對模型的操作只有一個url, 對於修改和刪除操作會在url後追加模型id便於標識
        // 如果在模型中定義了urlRoot, 伺服器介面應為[urlRoot/id]形式
        // 如果模型所屬的Collection集合定義了url方法或屬性, 則使用集合中的url形式: [collection.url/id]
        // 在訪問伺服器url時會在url後面追加上模型的id, 便於伺服器標識一條記錄, 因此模型中的id需要與伺服器記錄對應
        // 如果無法獲取模型或集合的url, 將呼叫urlError方法丟擲一個異常
        // 如果伺服器介面並沒有按照"PATHINFO"方式進行組織, 可以通過過載url方法實現與伺服器的無縫互動
        url: function () {
            // 定義伺服器對應的url路徑
            var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
            // 如果當前模型是客戶端新建的模型, 則不存在id屬性, 伺服器url直接使用base
            if (this.isNew())
                return base;
            // 如果當前模型具有id屬性, 可能是呼叫了save或destroy方法, 將在base後面追加模型的id
            // 下面將判斷base最後一個字元是否是"/", 生成的url格式為[base/id]
            return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
        },
        // parse方法用於解析從伺服器獲取的資料, 返回一個能夠被set方法解析的模型資料
        // 一般parse方法會根據伺服器返回的資料進行過載, 以便構建與伺服器的無縫連線
        // 當伺服器返回的資料結構與set方法所需的資料結構不一致(例如伺服器返回XML格式資料時), 可使用parse方法進行轉換
        parse: function (resp, xhr) {
            return resp;
        },
        // 建立一個新的模型, 它具有和當前模型相同的資料
        clone: function () {
            return new this.constructor(this.attributes);
        },
        // 檢查當前模型是否是客戶端建立的新模型
        // 檢查方式是根據模型是否存在id標識, 客戶端建立的新模型沒有id標識
        // 因此伺服器響應的模型資料中必須包含id標識, 標識的屬性名預設為"id", 也可以通過修改idAttribute屬性自定義標識
        isNew: function () {
            return this.id == null;
        },
        // 資料被更新時觸發change事件繫結的函式
        // 當set方法被呼叫, 會自動呼叫change方法, 如果在set方法被呼叫時指定了silent配置, 則需要手動呼叫change方法
        change: function (options) {
            // options必須是一個物件
            options || (options = {});
            // this._changing相關的邏輯有些問題
            // this._changing在方法最後被設定為false, 因此方法上面changing變數的值始終為false(第一次為undefined)
            // 作者的初衷應該是想用該變數標示change方法是否執行完畢, 對於瀏覽器端單執行緒的指令碼來說沒有意義, 因為該方法被執行時會阻塞其它指令碼
            // changing獲取上一次執行的狀態, 如果上一次指令碼沒有執行完畢, 則值為true
            var changing = this._changing;
            // 開始執行標識, 執行過程中值始終為true, 執行完畢後this._changing被修改為false
            this._changing = true;


            // 將非本次改變的資料狀態新增到_pending物件中
            for (var attr in this._silent)
                this._pending[attr] = true;


            // changes物件包含了當前資料上一次執行change事件至今, 已被改變的所有資料
            // 如果之前使用silent未觸發change事件, 則本次會被放到changes物件中
            var changes = _.extend({}, options.changes, this._silent);
            // 重置_silent物件
            this._silent = {};
            // 遍歷changes物件, 分別針對每一個屬性觸發單獨的change事件
            for (var attr in changes) {
                // 將Model物件, 屬性值, 配置項作為引數以此傳遞給事件的監聽函式
                this.trigger('change:' + attr, this, this.get(attr), options);
            }


            // 如果方法處於執行中, 則停止執行
            if (changing)
                return this;


            // 觸發change事件, 任意資料被改變後, 都會依次觸發"change:屬性"事件和"change"事件
            while (!_.isEmpty(this._pending)) {
                this._pending = {};
                // 觸發change事件, 並將Model例項和配置項作為引數傳遞給監聽函式
                this.trigger('change', this, options);
                // 遍歷changed物件中的資料, 並依次將已改變資料的狀態從changed中移除
                // 在此之後如果呼叫hasChanged檢查資料狀態, 將得到false(未改變)
                for (var attr in this.changed) {
                    if (this._pending[attr] || this._silent[attr])
                        continue;
                    // 移除changed中資料的狀態
                    delete this.changed[attr];
                }
                // change事件執行完畢, _previousAttributes屬性將記錄當前模型最新的資料副本
                // 因此如果需要獲取資料的上一個狀態, 一般只通過在觸發的change事件中通過previous或previousAttributes方法獲取
                this._previousAttributes = _.clone(this.attributes);
            }


            // 執行完畢標識
            this._changing = false;
            return this;
        },
        // 檢查某個資料是否在上一次執行change事件後被改變過
        /**
         * 一般在change事件中配合previous或previousAttributes方法使用, 如:
         * if(model.hasChanged('attr')) {
         *     var attrPrev = model.previous('attr');
         * }
         */
        hasChanged: function (attr) {
            if (!arguments.length)
                return !_.isEmpty(this.changed);
            return _.has(this.changed, attr);
        },
        // 獲取當前模型中的資料與上一次資料中已經發生變化的資料集合
        // (一般在使用silent屬性時沒有呼叫change方法, 因此資料會被臨時抱存在changed屬性中, 上一次的資料可通過previousAttributes方法獲取)
        // 如果傳遞了diff集合, 將使用上一次模型資料與diff集合中的資料進行比較, 返回不一致的資料集合
        // 如果比較結果中沒有差異, 則返回false
        changedAttributes: function (diff) {
            // 如果沒有指定diff, 將返回當前模型較上一次狀態已改變的資料集合, 這些資料已經被存在changed屬性中, 因此返回changed集合的一個副本
            if (!diff)
                return this.hasChanged() ? _.clone(this.changed) : false;
            // 指定了需要進行比較的diff集合, 將返回上一次的資料與diff集合的比較結果
            // old變數儲存了上一個狀態的模型資料
            var val, changed = false,
                old = this._previousAttributes;
            // 遍歷diff集合, 並將每一項與上一個狀態的集合進行比較
            for (var attr in diff) {
                // 將比較結果不一致的資料臨時儲存到changed變數
                if (_.isEqual(old[attr], (val = diff[attr])))
                    continue;
                (changed || (changed = {}))[attr] = val;
            }
            // 返回比較結果
            return changed;
        },
        // 在模型觸發的change事件中, 獲取某個屬性被改變前上一個狀態的資料, 一般用於進行資料比較或回滾
        // 該方法一般在change事件中呼叫, change事件被觸發後, _previousAttributes屬性存放最新的資料
        previous: function (attr) {
            // attr指定需要獲取上一個狀態的屬性名稱
            if (!arguments.length || !this._previousAttributes)
                return null;
            return this._previousAttributes[attr];
        },
        // 在模型觸發change事件中, 獲取所有屬性上一個狀態的資料集合
        // 該方法類似於previous()方法, 一般在change事件中呼叫, 用於資料比較或回滾
        previousAttributes: function () {
            // 將上一個狀態的資料物件克隆為一個新物件並返回
            return _.clone(this._previousAttributes);
        },
        // Check if the model is currently in a valid state. It's only possible to
        // get into an *invalid* state if you're using silent changes.
        // 驗證當前模型中的資料是否能通過validate方法驗證, 呼叫前請確保定義了validate方法
        isValid: function () {
            return !this.validate(this.attributes);
        },
        // 資料驗證方法, 在呼叫set, save, add等資料更新方法時, 被自動執行
        // 驗證失敗會觸發模型物件的"error"事件, 如果在options中指定了error處理函式, 則只會執行options.error函式
        // @param {Object} attrs 資料模型的attributes屬性, 儲存模型的物件化資料
        // @param {Object} options 配置項
        // @return {Boolean} 驗證通過返回true, 不通過返回false
        _validate: function (attrs, options) {
            // 如果在呼叫set, save, add等資料更新方法時設定了options.silent屬性, 則忽略驗證
            // 如果Model中沒有新增validate方法, 則忽略驗證
            if (options.silent || !this.validate)
                return true;
            // 獲取物件中所有的屬性值, 並放入validate方法中進行驗證
            // validate方法包含2個引數, 分別為模型中的資料集合與配置物件, 如果驗證通過則不返回任何資料(預設為undefined), 驗證失敗則返回帶有錯誤資訊資料
            attrs = _.extend({}, this.attributes, attrs);
            var error = this.validate(attrs, options);
            // 驗證通過
            if (!error)
                return true;
            // 驗證未通過
            // 如果配置物件中設定了error錯誤處理方法, 則呼叫該方法並將錯誤資料和配置物件傳遞給該方法
            if (options && options.error) {
                options.error(this, error, options);
            } else {
                // 如果對模型綁定了error事件監聽, 則觸發繫結事件
                this.trigger('error', this, error, options);
            }
            // 返回驗證未通過標識
            return false;
        }
    });


    // Backbone.Collection 資料模型集合相關
    // -------------------


    // Collection集合儲存一系列相同類的資料模型, 並提供相關方法對模型進行操作
    var Collection = Backbone.Collection = function (models, options) {
        // 配置物件
        options || (options = {});
        // 在配置引數中設定集合的模型類
        if (options.model)
            this.model = options.model;
        // 如果設定了comparator屬性, 則集合中的資料將按照comparator方法中的排序演算法進行排序(在add方法中會自動呼叫)
        if (options.comparator)
            this.comparator = options.comparator;
        // 例項化時重置集合的內部狀態(第一次呼叫時可理解為定義狀態)
        this._reset();
        // 呼叫自定義初始化方法, 如果需要一般會過載initialize方法
        this.initialize.apply(this, arguments);
        // 如果指定了models資料, 則呼叫reset方法將資料新增到集合中
        // 首次呼叫時設定了silent引數, 因此不會觸發"reset"事件
        if (models)
            this.reset(models, {
                silent: true,
                parse: options.parse
            });
    };
    // 通過extend方法定義集合類原型方法
    _.extend(Collection.prototype, Events, {


        // 定義集合的模型類, 模型類必須是一個Backbone.Model的子類
        // 在使用集合相關方法(如add, create等)時, 允許傳入資料物件, 集合方法會根據定義的模型類自動建立對應的例項
        // 集合中儲存的資料模型應該都是同一個模型類的例項
        model: Model,


        // 初始化方法, 該方法在集合例項被建立後自動呼叫
        // 一般會在定義集合類時過載該方法
        initialize: function () {},
        // 返回一個數組, 包含了集合中每個模型的資料物件
        toJSON: function (options) {
            // 通過Undersocre的map方法將集合中每一個模型的toJSON結果組成一個數組, 並返回
            return this.map(function (model) {
                // 依次呼叫每個模型物件的toJSON方法, 該方法預設將返回模型的資料物件(複製的副本)
                // 如果需要返回字串等其它形式, 可以過載toJSON方法
                return model.toJSON(options);
            });
        },
        // 向集合中新增一個或多個模型物件
        // 預設會觸發"add"事件, 如果在options中設定了silent屬性, 可以關閉此次事件觸發
        // 傳入的models可以是一個或一系列的模型物件(Model類的例項), 如果在集合中設定了model屬性, 則允許直接傳入資料物件(如 {name: 'test'}), 將自動將資料物件例項化為model指向的模型物件
        add: function (models, options) {
            // 區域性變數定義
            var i, index, length, model, cid, id, cids = {},
                ids = {},
                dups = [];
            options || (options = {});
            // models必須是一個數組, 如果只傳入了一個模型, 則將其轉換為陣列
            models = _.isArray(models) ? models.slice() : [models];


            // 遍歷需要新增的模型列表, 遍歷過程中, 將執行以下操作:
            // - 將資料物件轉化模型物件
            // - 建立模型與集合之間的引用
            // - 記錄無效和重複的模型, 並在後面進行過濾
            for (i = 0, length = models.length; i < length; i++) {
                // 將資料物件轉換為模型物件, 簡歷模型與集合的引用, 並存儲到model(同時models中對應的模型已經被替換為模型物件)
                if (!(model = models[i] = this._prepareModel(models[i], options))) {
                    throw new Error("Can't add an invalid model to a collection");
                }
                // 當前模型的cid和id
                cid = model.cid;
                id = model.id;
                // dups陣列中記錄了無效或重複的模型索引(models陣列中的索引), 並在下一步進行過濾刪除
                // 如果cids, ids變數中已經存在了該模型的索引, 則認為是同一個模型在傳入的models陣列中聲明瞭多次
                // 如果_byCid, _byId物件中已經存在了該模型的索引, 則認為同一個模型在當前集合中已經存在
                // 對於上述兩種情況, 將模型的索引記錄到dups進行過濾刪除
                if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
                    dups.push(i);
                    continue;
                }
                // 將models中已經遍歷過的模型記錄下來, 用於在下一次迴圈時進行重複檢查
                cids[cid] = ids[id] = model;
            }


            // 從models中刪除無效或重複的模型, 保留目前集合中真正需要新增的模型列表
            i = dups.length;
            while (i--) {
                models.splice(dups[i], 1);
            }


            // 遍歷需要新增的模型, 監聽模型事件並記錄_byCid, _byId列表, 用於在呼叫get和getByCid方法時作為索引
            for (i = 0, length = models.length; i < length; i++) {
                // 監聽模型中的所有事件, 並執行_onModelEvent方法
                // _onModelEvent方法中會對模型丟擲的add, remove, destroy和change事件進行處理, 以便模型與集合中的狀態保持同步
                (model = models[i]).on('all', this._onModelEvent, this);
                // 將模型根據cid記錄到_byCid物件, 便於根據cid進行查詢
                this._byCid[model.cid] = model;
                // 將模型根據id記錄到_byId物件, 便於根據id進行查詢
                if (model.id != null)
                    this._byId[model.id] = model;
            }


            // 改變集合的length屬性, length屬性記錄了當前集合中模型的數量
            this.length += length;
            // 設定新模型列表插入到集合中的位置, 如果在options中設定了at引數, 則在集合的at位置插入
            // 預設將插入到集合的末尾
            // 如果設定了comparator自定義排序方法, 則設定at後還將按照comparator中的方法進行排序, 因此最終的順序可能並非在at指定的位置
            index = options.at != null ? options.at : this.models.length;
            splice.apply(this.models, [index, 0].concat(models));
            // 如果設定了comparator方法, 則將資料按照comparator中的演算法進行排序
            // 自動排序使用silent屬性阻止觸發reset事件
            if (this.comparator)
                this.sort({
                    silent: true
                });
            // 依次對每個模型物件觸發"add"事件, 如果設定了silent屬性, 則阻止事件觸發
            if (options.silent)
                return this;
            // 遍歷新增加的模型列表
            for (i = 0, length = this.models.length; i < length; i++) {
                if (!cids[(model = this.models[i]).cid])
                    continue;
                options.index = i;
                // 觸發模型的"add"事件, 因為集合監聽了模型的"all"事件, 因此在_onModelEvent方法中, 集合也將觸發"add"事件
                // 詳細資訊可參考Collection.prototype._onModelEvent方法
                model.trigger('add', model, this, options);
            }
            return this;
        },
        // 從集合中移除模型物件(支援移除多個模型)
        // 傳入的models可以是需要移除的模型物件, 或模型的cid和模型的id
        // 移除模型並不會呼叫模型的destroy方法
        // 如果沒有設定options.silent引數, 將觸發模型的remove事件, 同時將觸發集合的remove事件(集合通過_onModelEvent方法監聽了模型的所有事件)
        remove: function (models, options) {
            var i, l, index, model;
            // options預設為空物件
            options || (options = {});
            // models必須是陣列型別, 當只移除一個模型時, 將其放入一個數組
            models = _.isArray(models) ? models.slice() : [models];
            // 遍歷需要移除的模型列表
            for (i = 0, l = models.length; i < l; i++) {
                // 所傳入的models列表中可以是需要移除的模型物件, 或模型的cid和模型的id
                // (在getByCid和get方法中, 可通過cid, id來獲取模型, 如果傳入的是一個模型物件, 則返回模型本身)
                model = this.getByCid(models[i]) || this.get(models[i]);
                // 沒有獲取到模型
                if (!model)
                    continue;
                // 從_byId列表中移除模型的id引用
                delete this._byId[model.id];
                // 從_byCid列表中移除模型的cid引用
                delete this._byCid[model.cid];
                // indexOf是Underscore物件中的方法, 這裡通過indexOf方法獲取模型在集合中首次出現的位置
                index = this.indexOf(model);
                // 從集合列表中移除該模型
                this.models.splice(index, 1);
                // 重置當前集合的length屬性(記錄集合中模型的數量)
                this.length--;
                // 如果沒有設定silent屬性, 則觸發模型的remove事件
                if (!options.silent) {
                    // 將當前模型在集合中的位置新增到options物件並傳遞給remove監聽事件, 以便在事件函式中可以使用
                    options.index = index;
                    model.trigger('remove', model, this, options);
                }
                // 解除模型與集合的關係, 包括集合中對模型的引用和事件監聽
                this._removeReference(model);
            }
            return this;
        },
        // 向集合的末尾新增模型物件
        // 如果集合類中定義了comparator排序方法, 則通過push方法新增的模型將按照comparator定義的演算法進行排序, 因此模型順序可能會被改變
        push: function (model, options) {
            // 通過_prepareModel方法將model例項化為模型物件, 這句程式碼是多餘的, 因為在下面呼叫的add方法中還會通過_prepareModel獲取一次模型
            model = this._prepareModel(model, options);
            // 呼叫add方法將模型新增到集合中(預設新增到集合末尾)
            this.add(model, options);
            return model;
        },
        // 移除集合中最後一個模型物件
        pop: function (options) {
            // 獲取集合中最後一個模型
            var model = this.at(this.length - 1);
            // 通過remove方法移除該模型
            this.remove(model, options);
            return model;
        },
        // 向集合的第一個位置插入模型
        // 如果集合類中定義了comparator排序方法, 則通過unshift方法新增的模型將按照comparator定義的演算法進行排序, 因此模型順序可能會被改變
        unshift: function (model, options) {
            // 通過_prepareModel方法將model例項化為模型物件
            model = this._prepareModel(model, options);
            // 呼叫add方法將模型插入到集合的第一個位置(設定at為0)
            // 如果定義了comparator排序方法, 集合的順序將被重排
            this.add(model, _.extend({
                at: 0
            }, options));
            return model;
        },
        // 移除並返回集合中的第一個模型物件
        shift: function (options) {
            // 獲得集合中的第一個模型
            var model = this.at(0);
            // 從集合中刪除該模型
            this.remove(model, options);
            // 返回模型物件
            return model;
        },
        // 根據id從集合中查詢模型並返回
        get: function (id) {
            if (id == null)
                return
            void 0;
            return this._byId[id.id != null ? id.id : id];
        },
        // 根據cid從集合中查詢模型並返回
        getByCid: function (cid) {
            return cid && this._byCid[cid.cid || cid];
        },
        // 根據索引(下標, 從0開始)從集合中查詢模型並返回
        at: function (index) {
            return this.models[index];
        },
        // 對集合中的模型根據值進行篩選
        // attrs是一個篩選物件, 如 {name: 'Jack'}, 將返回集合中所有name為"Jack"的模型(陣列)
        where: function (attrs) {
            // attrs不能為空值
            if (_.isEmpty(attrs))
                return [];
            // 通過filter方法對集合中的模型進行篩選
            // filter方法是Underscore中的方法, 用於將遍歷集合中的元素, 並將能通過處理器驗證(返回值為true)的元素作為陣列返回
            return this.filter(function (model) {
                // 遍歷attrs物件中的驗證規則
                for (var key in attrs) {
                    // 將attrs中的驗證規則與集合中的模型進行匹配
                    if (attrs[key] !== model.get(key))
                        return false;
                }
                return true;
            });
        },
        // 對集合中的模型按照comparator屬性指定的方法進行排序
        // 如果沒有在options中設定silent引數, 則排序後將觸發reset事件
        sort: function (options) {
            // options預設是一個物件
            options || (options = {});
            // 呼叫sort方法必須指定了comparator屬性(排序演算法方法), 否則將丟擲一個錯誤
            if (!this.comparator)
                throw new Error('Cannot sort a set without a comparator');
            // boundComparator儲存了綁定當前集合上下文物件的comparator排序演算法方法
            var boundComparator = _.bind(this.comparator, this);
            if (this.comparator.length == 1) {
                this.models = this.sortBy(boundComparator);
            } else {
                // 呼叫Array.prototype.sort通過comparator演算法對資料進行自定義排序
                this.models.sort(boundComparator);
            }
            // 如果沒有指定silent引數, 則觸發reset事件
            if (!options.silent)
                this.trigger('reset', this, options);
            return this;
        },
        // 將集合中所有模型的attr屬性值存放到一個數組並返回
        pluck: function (attr) {
            // map是Underscore中的方法, 用於遍歷一個集合, 並將所有處理器的返回值作為一個數組返回
            return _.map(this.models, function (model) {
                // 返回當前模型的attr屬性值
                return model.get(attr);
            });
        },
        // 替換集合中的所有模型資料(models)
        // 該操作將刪除集合中當前的所有資料和狀態, 並重新將資料設定為models
        // models應該是一個數組, 可以包含一系列Model模型物件, 或原始物件(將在add方法中自動建立為模型物件)
        reset: function (models, options) {
            // models是進行替換的模型(或資料)陣列
            models || (models = []);
            // options預設是一個空物件
            options || (options = {});
            // 遍歷當前集合中的模型, 依次刪除並解除它們與集合的引用關係
            for (var i = 0, l = this.models.length; i < l; i++) {
                this._removeReference(this.models[i]);
            }
            // 刪除集合資料並重置狀態
            this._reset();
            // 通過add方法將新的模型資料新增到集合
            // 這裡通過exnted方法將配置項覆蓋到一個新的物件, 該物件預設silent為true, 因此不會觸發"add"事件
            // 如果在呼叫reset方法時沒有設定silent屬性則會觸發reset事件, 如果設定為true則不會觸發任何事件, 如果設定為false, 將依次觸發"add"和"reset"事件
            this.add(models, _.extend({
                silent: true
            }, options));
            // 如果在呼叫reset方法時沒有設定silent屬性, 則觸發reset事件
            if (!options.silent)
                this.trigger('reset', this, options);
            return this;
        },
        // 從伺服器獲取集合的初始化資料
        // 如果在options中設定引數add=true, 則獲取到的資料會被追加到集合中, 否則將以伺服器返回的資料替換集合中的當前資料
        fetch: function (options) {
            // 複製options物件, 因為options物件在後面會被修改用於臨時儲存資料
            options = options ? _.clone(options) : {};
            if (options.parse === undefined)
                options.parse = true;
            // collection記錄當前集合物件, 用於在success回撥函式中使用
            var collection = this;
            // 自定義回撥函式, 資料請求成功後並新增完成後, 會呼叫自定義success函式
            var success = options.success;
            // 當從伺服器請求資料成功時執行options.success, 該函式中將解析並新增資料
            options.success = function (resp, status, xhr) {
                // 通過parse方法對伺服器返回的資料進行解析, 如果需要自定義資料結構, 可以過載parse方法
                // 如果在options中設定add=true, 則呼叫add方法將資料新增到集合, 否則將通過reset方法將集合中的資料替換為伺服器的返回資料
                collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
                // 如果設定了自定義成功回撥, 則執行
                if (success)
                    success(collection, resp);
            };
            // 當伺服器返回狀態錯誤時, 通過wrapError方法處理錯誤事件
            options.error = Backbone.wrapError(options.error, collection, options);
            // 呼叫Backbone.sync方法傳送請求從伺服器獲取資料
            // 如果需要的資料並不是從伺服器獲取, 或獲取方式不使用AJAX, 可以過載Backbone.sync方法
            return (this.sync || Backbone.sync).call(this, 'read', this, options);
        },
        // 向集合中新增並建立一個模型, 同時將該模型儲存到伺服器
        // 如果是通過資料物件來建立模型, 需要在集合中宣告model屬性對應的模型類
        // 如果在options中聲明瞭wait屬性, 則會在伺服器建立成功後再將模型新增到集合, 否則先將模型新增到集合, 再儲存到伺服器(無論儲存是否成功)
        create: function (model, options) {
            var coll = this;
            // 定義options物件
            options = options ? _.clone(options) : {};
            // 通過_prepareModel獲取模型類的例項
            model = this._prepareModel(model, options);
            // 模型建立失敗
            if (!model)
                return false;
            // 如果沒有宣告wait屬性, 則通過add方法將模型新增到集合中
            if (!options.wait)
                coll.add(model, options);
            // success儲存儲存到伺服器成功之後的自定義回撥函式(通過options.success宣告)
            var success = options.success;
            // 監聽模型資料儲存成功後的回撥函式
            options.success = function (nextModel, resp, xhr) {
                // 如果聲明瞭wait屬性, 則在只有在伺服器儲存成功後才會將模型新增到集合中
                if (options.wait)
                    coll.add(nextModel, options);
                // 如果聲明瞭自定義成功回撥, 則執行自定義函式, 否則將預設觸發模型的sync事件
                if (success) {
                    success(nextModel, resp);
                } else {
                    nextModel.trigger('sync', model, resp, options);
                }
            };
            // 呼叫模型的save方法, 將模型資料儲存到伺服器
            model.save(null, options);
            return model;
        },
        // 資料解析方法, 用於將伺服器資料解析為模型和集合可用的結構化資料
        // 預設將返回resp本身, 這需要與伺服器定義Backbone支援的資料格式, 如果需要自定義資料格式, 可以過載parse方法
        parse: function (resp, xhr) {
            return resp;
        },
        // chain用於構建集合資料的鏈式操作, 它將集合中的資料轉換為一個Underscore物件, 並使用Underscore的chain方法轉換為鏈式結構
        // 關於chain方法的轉換方式, 可參考Underscore中chain方法的註釋
        chain: function () {
            return _(this.models).chain();
        },
        // 刪除所有集合元素並重置集合中的資料狀態
        _reset: function (options) {
            // 刪除集合元素
            this.length = 0;
            this.models = [];
            // 重置集合狀態
            this._byId = {};
            this._byCid = {};
        },
        // 將模型新增到集合中之前的一些準備工作
        // 包括將資料例項化為一個模型物件, 和將集合引用到模型的collection屬性
        _prepareModel: function (model, options) {
            options || (options = {});
            // 檢查model是否是一個模型物件(即Model類的例項