首頁 > web前端 > js教程 > 主體

Backbone.js 0.9.2 原始碼註解中文翻譯版_基礎知識

WBOY
發布: 2016-05-16 15:53:06
原創
1345 人瀏覽過
// 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屬性進行依序關聯

        /**資料格式如:

         * {

         * 尾部:{對象},

         * 下一個: {

         * 回呼:{函數},

         * 上下文:{物件},

         * 下一個: {

         * 回呼:{函數},

         * 上下文:{物件},

         * 下一個:{物件}

         * }

         * }

         * }*/

        // 列表每一層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

  /*** @格式選項

   * {

   * 解析:{布林值},

   * 收藏:{收藏}

   * }*/

  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

    有 : 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 
登入後複製
相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板