首頁 web前端 js教程 Backbone.js 0.9.2 原始碼註解中文翻譯版_基礎知識

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

May 16, 2016 pm 03:53 PM
中文

// 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 
登入後複製
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

使命召喚戰區手遊怎麼設定中文 使命召喚戰區手遊怎麼設定中文 Mar 22, 2024 am 08:41 AM

使命召喚戰區作為全新上線的一款手遊,有很多的玩家都非常的好奇要怎麼樣才能夠將這款遊戲的語言設定為中文,其實非常的簡單,玩家只需要下載中文的語言包,隨後進行使用就可以進行修改了,詳細的內容可以在這篇中文設定方法介紹之中進行了解,讓我們一起來看看吧。使命召喚戰區手遊怎麼設定中文1、先進入遊戲,點選介面右上角的設定圖示。 2.在出現的選單列中,找到【Download】這個選項並且點選。 3.在這個頁面中選擇【SIMPLIFIEDCHINESE】(簡體中文),就可以對簡體中文的安裝包進行下載了。 4.回到設

VSCode 設定中文:完全指南 VSCode 設定中文:完全指南 Mar 25, 2024 am 11:18 AM

VSCode設定中文:完整指南在軟體開發中,VisualStudioCode(簡稱VSCode)是一個常用的整合開發環境。對於使用中文的開發者來說,將VSCode設定為中文介面可以提升工作效率。本文將為大家提供一個完整的指南,詳細介紹如何將VSCode設定為中文介面,並提供具體的程式碼範例。第一步:下載安裝語言包開啟VSCode後,點選左

Excel表格怎麼設定顯示中文? Excel切換中文操作教學 Excel表格怎麼設定顯示中文? Excel切換中文操作教學 Mar 14, 2024 pm 03:28 PM

Excel表格是現在很多人都在使用的辦公室軟體之一,有些使用者因為電腦是win11系統,因此顯示的是英文介面,想要切換成中文介面,但是不知道該怎麼操作,針對這個問題,本期小編就來為廣大用戶們回答,一起來看看今日軟體教學所分享的內容。  Excel切換中文操作教學:  1、進入軟體,點選頁面上方工具列左側的「File」選項。  2、在下方給出的選項中選擇「options」。  3、進入新介面後,點選左側的「language」選項

如何在PHP Dompdf中正確顯示中文字符 如何在PHP Dompdf中正確顯示中文字符 Mar 05, 2024 pm 01:03 PM

如何在PHPDompdf中正確顯示中文字元在使用PHPDompdf產生PDF檔案時,遇到中文字元顯示亂碼的問題是一個常見的挑戰。這是因為Dompdf預設使用的字體庫中不包含中文字元集。為了正確顯示中文字符,我們需要手動設定Dompdf的字體,並確保選擇支援中文字符的字體。以下是一些具體的步驟和程式碼範例來解決這個問題:第一步:下載中文字體檔案首先,我們需要

修復PHP Dompdf中文亂碼的有效途徑 修復PHP Dompdf中文亂碼的有效途徑 Mar 05, 2024 pm 04:45 PM

標題:修復PHPDompdf中文亂碼的有效途徑在使用PHPDompdf產生PDF文件時,中文字元出現亂碼是一個常見的問題。這問題通常源自於Dompdf預設不支援中文字元集,導致中文內容無法正確顯示。為了解決這個問題,我們需要採取一些有效的途徑來修復PHPDompdf中文亂碼的問題。 1.使用自訂字型檔案一個解決Dompdf中文亂碼問題的有效方法是使用

wwe2k24會有中文嗎 wwe2k24會有中文嗎 Mar 13, 2024 pm 04:40 PM

《WWE2K24》乃由VisualConcepts傾力打造的競速運動遊,已於2024年3月9日正式問世。此款遊戲倍受讚譽,許多玩家熱切關注其是否設有中文版。遺憾的是,迄今為止,《WWE2K24》尚未推出中文語言版本。 wwe2k24會有中文嗎答:目前不支援中文。 WWE2K24在Steam國區的標準版售價為199元,豪華版為329元,紀念版為395元。遊戲的配置需求較高,無論處理器、顯示卡或運行記憶體等方面,均有一定標準。官方推薦配置以及最低配置介紹:

如何將windows 7的語言設定為中文 如何將windows 7的語言設定為中文 Dec 21, 2023 pm 10:07 PM

有些朋友可能會在安裝系統時不小心設定成了英文,結果所有介面都變成了英文,看都看不懂。其實我們可以在控制面板中設定語言,將語言改為中文,下面就一起來看看更改的方法吧。 win7如何更改語言為中文1、先點選畫面左下角的按鈕,然後選擇「ControlPanel」2、找到「Clock,Language,andRegion」下的「Changedispalylanguage」3、點選下方「English」就可以在下拉式選單中選擇簡體中文了。 4.確定之後點選「Logoffnow」登出並重新啟動電腦。 5.回來之後

解決PHP寫入txt檔案中文亂碼的技巧 解決PHP寫入txt檔案中文亂碼的技巧 Mar 27, 2024 pm 01:18 PM

解決PHP寫入txt檔案中文亂碼的技巧隨著網路的快速發展,PHP作為一種廣泛應用的程式語言,被越來越多的開發者所使用。在PHP開發中,經常需要對文字檔案進行讀寫操作,其中包括寫入中文內容的txt檔案。然而,由於編碼格式的問題,有時會導致寫入的中文出現亂碼。本文將介紹一些解決PHP寫入txt檔案中文亂碼的技巧,並提供具體的程式碼範例。問題分析在PHP中,文本

See all articles