Backbone.js 0.9.2 Source code annotation Chinese translation version

  • 2020-06-22 23:38:59
  • OfStack


// 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() {

 

  //  create 1 Global object ,  Is represented in the browser as window object ,  in Node.js In the said global object 

  var root = this;

 

  //  save "Backbone" The value before the variable is overridden 

  //  If a naming conflict occurs or specification is considered ,  through Backbone.noConflict() Method to restore the variable by Backbone Take the previous value ,  And return Backbone Object to rename 

  var previousBackbone = root.Backbone;

 

  //  will Array.prototype In the slice and splice Method is cached in a local variable for invocation 

  var slice = Array.prototype.slice;

  var splice = Array.prototype.splice;

 

  var Backbone;

  if( typeof exports !== 'undefined') {

    Backbone = exports;

  } else {

    Backbone = root.Backbone = {};

  }

 

  //  define Backbone version 

  Backbone.VERSION = '0.9.2';

 

  //  Automatic import in the server environment Underscore,  in Backbone Parts of a method depend on or inherit from Underscore

  var _ = root._;

  if(!_ && ( typeof require !== 'undefined'))

    _ = require('underscore');

 

  //  Define the first 3 Party libraries for the system 1 The variables of "$",  Used in view (View),  Event processing and synchronization with server data (sync) Calls a method in the library 

  //  Supported libraries include jQuery, Zepto Etc. ,  They have the same syntax ,  but Zepto More suitable for mobile development ,  It's aimed at Webkit Kernel browser 

  //  It can also be customized 1 with jQuery Custom libraries with similar syntax ,  for Backbone use ( Sometimes we might 1 than jQuery, Zepto A lighter custom version )

  //  The one defined here "$" Is a local variable ,  So it won't affect in Backbone Out-of-frame control 3 Normal use of square library 

  var $ = root.jQuery || root.Zepto || root.ender;

 

  //  Manual setting control 3 Party libraries 

  //  If it's imported Backbone No control was imported before 3 Party libraries ,  Can be achieved by setDomLibrary Methods set up "$" A local variable 

  // setDomLibrary Methods are also commonly used in Backbone Dynamically import custom libraries in 

  Backbone.setDomLibrary = function(lib) {

    $ = lib;

  };

  //  Give up to "Backbone" Naming framework ,  And return Backbone object , 1 Commonly used to avoid naming conflicts or to standardize naming conventions 

  //  For example, :

  // var bk = Backbone.noConflict(); //  cancel "Backbone" named ,  And will be Backbone Objects are stored in bk variable 

  // console.log(Backbone); //  The variable is no longer accessible Backbone object ,  And return to Backbone The value before the definition 

  // var MyBackbone = bk; //  while bk Store the Backbone object ,  Let's rename it MyBackbone

  Backbone.noConflict = function() {

    root.Backbone = previousBackbone;

    return this;

  };

  //  For non-support REST Mode browser ,  You can set the Backbone.emulateHTTP = true

  //  A request with the server will be made POST Way to send ,  And add it to the data _method Parameter identifies the operation name ,  It will also be sent X-HTTP-Method-Override Header information 

  Backbone.emulateHTTP = false;

 

  //  For non-support application/json Coded browser ,  You can set the Backbone.emulateJSON = true;

  //  Set the request type to application/x-www-form-urlencoded,  And place the data in model Parameter to achieve compatibility 

  Backbone.emulateJSON = false;

 

  // Backbone.Events  Custom event correlation 

  // -----------------

 

  // eventSplitter Specifies when to process more than one event ,  Rules for resolving event names 

  var eventSplitter = /\s+/;

 

  //  Custom event manager 

  //  By binding in an object Events Relevant methods ,  Allows to add to an object ,  Delete and trigger custom events 

  var Events = Backbone.Events = {

 

    //  Events will be customized (events) And callback functions (callback) Binds to the current object 

    //  The context object in the callback function is specified context,  If it's not set context The context object defaults to the object of the current binding event 

    //  The method is similar to DOM Level2 In the addEventListener methods 

    // events Allows you to specify multiple event names ,  Separated by whitespace characters ( Such as the blank space ,  Tabs, etc. )

    //  When the event name is "all" when ,  In the call trigger Method triggers any event ,  Will call "all" All of the bound callback functions in the event 

    on : function(events, callback, context) {

      //  define 1 Local variables used in some functions 

      var calls, event, node, tail, list;

      //  You must set up callback The callback function 

      if(!callback)

        return this;

      //  through eventSplitter Resolve the event name ,  use split Split multiple event names into 1 An array 

      // 1 Use white space characters to specify multiple event names 

      events = events.split(eventSplitter);

      // calls Records a list of bound events and callbacks in the current object 

      calls = this._callbacks || (this._callbacks = {});

 

      //  List of circular event names ,  Store event names from beginning to end event variable 

      while( event = events.shift()) {

        //  Get bound event Event callback function 

        // list Stores bindings in a single event name callback List of callback functions 

        //  The list of functions is not stored as an array ,  It goes through multiple objects next Attributes are correlated in turn 

        /**  Data formats such as :

         * {

         *   tail: {Object},

         *   next: {

         *     callback: {Function},

         *     context: {Object},

         *     next: {

         *       callback: {Function},

         *       context: {Object},

         *       next: {Object}

         *     }

         *   }

         * }

         */

        //  List each 1 layer next The object is stored 1 Information about the secondary callback event ( The body of the function ,  Context and under 1 The secondary callback event )

        //  The top level of the event list is stored 1 a tail object ,  It stores the end 1 The identity of the secondary binding callback event ( And the last 1 Of the secondary callback event next Point to the same 1 An object )

        //  through tail logo ,  You can tell when you've reached the end by walking through the callback list 1 A callback function 

        list = calls[event];

        // node Variables are used to record information about the callback function 

        // tail Only store the last 1 The identity of the secondary binding callback function 

        //  So if you've already bound a callback function before ,  Will be the previous tail Assigned to the node As a 1 Object usage ,  Then create a 1 A new object is identified to tail

        //  The reason for adding this callback event to 1 The callback tail object ,  The object hierarchy for the list of callback functions is arranged in binding order ( The most recently bound events are placed at the bottom )

        node = list ? list.tail : {};

        node.next = tail = {};

        //  Record the function body and context information for this callback 

        node.context = context;

        node.callback = callback;

        //  Reassemble the callback list for the current event ,  The callback event has been added to the list 

        calls[event] = {

          tail : tail,

          next : list ? list.next : node

        };

      }

      //  Returns the current object ,  Facilitate method chain calls 

      return this;

    },

    //  Removes a bound event or callback function from the object ,  Can be achieved by events, callback and context Filter events or callbacks that need to be removed 

    // -  if context Is empty ,  Remove all of them callback Specified function 

    // -  if callback Is empty ,  Removes all callbacks in the event 

    // -  if events Is empty ,  But specifies the callback or context,  The remove callback or context The specified callback function ( Does not distinguish between event names )

    // -  If no parameters are passed ,  Removes all bound events and callbacks from the object 

    off : function(events, callback, context) {

      var event, calls, node, tail, cb, ctx;

 

      // No events, or removing *all* events.

      //  The current object is not bound to any event 

      if(!( calls = this._callbacks))

        return;

      //  If no parameters are specified ,  Removes all events and callbacks ( delete _callbacks attribute )

      if(!(events || callback || context)) {

        delete this._callbacks;

        return this;

      }

 

      //  Resolves the list of events that need to be removed 

      // -  If you specify events,  According to eventSplitter Parse the event name 

      // -  If not specified events,  Resolves a list of names for all events that have been bound 

      events = events ? events.split(eventSplitter) : _.keys(calls);

 

      //  List of circular event names 

      while( event = events.shift()) {

        //  Removes the current event object from the list ,  And the cache to node variable 

        node = calls[event];

        delete calls[event];

        //  If the current event object does not exist ( Or there is no removal filter condition specified ,  The current event and all callbacks are removed ),  Terminates the operation ( The event object is on 1 Steps have been removed )

        if(!node || !(callback || context))

          continue;

        // Create a new list, omitting the indicated callbacks.

        //  Filter conditions based on callback functions or context ,  The assembly 1 A new event object and rebinding 

        tail = node.tail;

        //  Iterate through all the callback objects in the event 

        while(( node = node.next) !== tail) {

          cb = node.callback;

          ctx = node.context;

          //  Depending on the callback function and context in the argument ,  Filter the callback function ,  Rebind the callback function that does not meet the filter criteria to the event ( Because all the callback functions in the event have been removed above )

          if((callback && cb !== callback) || (context && ctx !== context)) {

            this.on(event, cb, ctx);

          }

        }

      }

 

      return this;

    },

    //  Triggers a defined one 1 One or more events ,  List of callback functions that execute the binding in turn 

    trigger : function(events) {

      var event, node, calls, tail, args, all, rest;

      //  The current object is not bound to any event 

      if(!( calls = this._callbacks))

        return this;

      //  Gets the bound in the list of callback functions "all" The event list 

      all = calls.all;

      //  The name of the event that will need to be fired ,  In accordance with the eventSplitter Rule resolution is 1 An array 

      events = events.split(eventSplitter);

      //  will trigger From the first 2 The number of arguments after ,  recorded rest variable ,  Will in turn be passed to the callback function 

      rest = slice.call(arguments, 1);

 

      //  A list of events that the loop needs to fire 

      while( event = events.shift()) {

        //  Here the node A variable records a list of all callback functions for the current event 

        if( node = calls[event]) {

          // tail Variable recording end 1 The object identity of the secondary bound event 

          tail = node.tail;

          // node The value of the variable ,  In the order in which the events are bound ,  A single callback event object that is, in turn, assigned to a binding 

          //  The last 1 Secondary bound events next attribute ,  with tail With reference to 1 An object ,  This is used to determine whether the end of the list has been reached 

          while(( node = node.next) !== tail) {

            //  Execute all bound events ,  And will call trigger Is passed to the callback function 

            node.callback.apply(node.context || this, rest);

          }

        }

        //  variable all The binding time is recorded "all" The event ,  That is, when any event is called , "all" The callback functions in the event are executed 

        // - "all" Callbacks in events regardless of binding order ,  This is done after the list of callback functions for the current event has all been executed 

        // - "all" Events should be called automatically when normal events are triggered ,  If you force it to trigger "all" The event ,  The callback function in the event will be executed twice 

        if( node = all) {

          tail = node.tail;

          //  The difference is that it calls a callback function to a normal event , all The event takes the name of the event currently being called as the first 1 Three arguments are passed to the callback function 

          args = [event].concat(rest);

          //  Iterate and execute "all" List of callback functions in the event 

          while(( node = node.next) !== tail) {

            node.callback.apply(node.context || this, args);

          }

        }

      }

 

      return this;

    }

  };

 

  //  Alias for binding and releasing events ,  Also for compatibility Backbone Previous version 

  Events.bind = Events.on;

  Events.unbind = Events.off;

 

  // Backbone.Model  Data object model 

  // --------------

 

  // Model is Backbone The base class for all data object models in ,  Used to create 1 Data model 

  // @param {Object} attributes  Specifies the initialization data when the model is created 

  // @param {Object} options

  /**

   * @format options

   * {

   *   parse: {Boolean},

   *   collection: {Collection}

   * }

   */

  var Model = Backbone.Model = function(attributes, options) {

    // defaults Variables are used to store default data for the model 

    var defaults;

    //  If not specified attributes parameter ,  Is set attributes Null object 

    attributes || ( attributes = {});

    //  Set up the attributes The default method for parsing data ,  For example, the default data is fetched from the server ( Or the original data is XML format ),  In order to compatible set The format of the data required by the ,  You can use parse Method analysis 

    if(options && options.parse)

      attributes = this.parse(attributes);

    if( defaults = getValue(this, 'defaults')) {

      //  if Model Set at definition time defaults The default data ,  Then initialize data usage defaults with attributes Data after parameter merging (attributes The data will be overwritten defaults Data with the same name )

      attributes = _.extend({}, defaults, attributes);

    }

    //  Explicitly specify to which the model belongs Collection object ( In the call Collection the add, push When you add a model to a collection of methods ,  The model is automatically set to Collection object )

    if(options && options.collection)

      this.collection = options.collection;

    // attributes Property stores the current model's JSON Objectified data ,  The model is created empty by default 

    this.attributes = {};

    //  define _escapedAttributes Cache object ,  It will cache through escape Method to process data 

    this._escapedAttributes = {};

    //  For each 1 Model configuration 1 A wei 1 logo 

    this.cid = _.uniqueId('c');

    //  define 1 A collection of objects used to record the state of the data ,  Refer to the comments at the time the object is defined 

    this.changed = {};

    this._silent = {};

    this._pending = {};

    //  Sets initialization data when an instance is created ,  First set use silent parameter ,  Not trigger change The event 

    this.set(attributes, {

      silent : true

    });

    //  The initialization data has been set above , changed, _silent, _pending The state of the object may have changed ,  I'm going to reinitialize here 

    this.changed = {};

    this._silent = {};

    this._pending = {};

    // _previousAttributes Variables that store model data 1 A copy of the 

    //  For use in change Event to retrieve the state of model data before it is changed ,  through previous or previousAttributes Method acquisition 1 The data of the states 

    this._previousAttributes = _.clone(this.attributes);

    //  call initialize Initialization method 

    this.initialize.apply(this, arguments);

  };

  //  use extend Methods for Model The prototype defines 1 Series of properties and methods 

  _.extend(Model.prototype, Events, {

 

    // changed Property records each call set methods ,  Altered data key A collection of 

    changed : null,

 

    // //  When specifying silent When the attribute ,  Not trigger change The event ,  The changed data is recorded ,  Until the 1 Once the trigger change The event 

    // _silent Property is used to record usage silent The changed data of 

    _silent : null,

 

    _pending : null,

 

    //  The unique of each model 1 Identifying property ( The default is "id",  By modifying the idAttribute Can be customized id The property name )

    //  If included when setting data id attribute ,  the id Will override the model id

    // id For use in Collection Find and identify models in a collection ,  When communicating with the background interface id As a 1 The identity of a bar record 

    idAttribute : 'id',

 

    //  Model initialization method ,  Automatically called after the model has been constructed 

    initialize : function() {

    },

    //  Returns the data in the current model 1 A copy of the (JSON Object format )

    toJSON : function(options) {

      return _.clone(this.attributes);

    },

    //  According to the attr The property name ,  Gets the data values in the model 

    get : function(attr) {

      return this.attributes[attr];

    },

    //  According to the attr The property name ,  Gets the data values in the model ,  Contains data values HTML Special characters will be converted to HTML entity ,  contains  & < > " ' \

    //  through  _.escape Method implementation 

    escape : function(attr) {

      var html;

      //  from _escapedAttributes Find the data in the cache object ,  Returns directly if the data has been cached 

      if( html = this._escapedAttributes[attr])

        return html;

      // _escapedAttributes No data was found in the cache object 

      //  Get the data from the model first 

      var val = this.get(attr);

      //  Take the data HTML use  _.escape Method to an entity ,  And the cache to _escapedAttributes object ,  So you can get it directly next time 

      return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);

    },

    //  Check if an attribute exists in the model ,  When the value of the property is converted to Boolean The post-type value is false,  It doesn't exist 

    //  If the value is false, null, undefined, 0, NaN,  Or an empty string ,  Will be converted to false

    has : function(attr) {

      return this.get(attr) != null;

    },

    //  Set the data in the model ,  if key Value does not exist ,  Is added to the model as a new property ,  if key Value already exists ,  Changes to the new value 

    set : function(key, value, options) {

      // attrs The variable records the data objects that need to be set 

      var attrs, attr, val;

 

      //  Parameter form allows key-value Objects form ,  Or through key, value The two parameters are set separately 

      //  if key is 1 An object ,  Is determined to use object form Settings ,  The first 2 The parameters will be treated as options parameter 

      if(_.isObject(key) || key == null) {

        attrs = key;

        options = value;

      } else {

        //  through key, value The two parameters are set separately ,  Put the data in attrs Object convenience system 1 To deal with 

        attrs = {};

        attrs[key] = value;

      }

 

      // options The configuration item must be 1 An object ,  If it's not set options The default value is 1 An empty object 

      options || ( options = {});

      //  No action is performed when no parameters are set 

      if(!attrs)

        return this;

      //  If the data object being set belongs to Model Of the class 1 An instance ,  will Model The object's attributes Data object assignment attrs

      // 1 As in the copy 1 a Model Object data to another 1 a Model When the object ,  The action is performed 

      if( attrs instanceof Model)

        attrs = attrs.attributes;

      //  if options Config object unset attribute ,  will attrs All properties in the data object are reset to undefined

      // 1 As in the copy 1 a Model Object data to another 1 a Model When the object ,  But you just have to copy Model This operation is performed when the data in the 

      if(options.unset)

        for(attr in attrs)

        attrs[attr] =

        void 0;

 

      //  Validate the current data ,  If the validation fails, the execution is stopped 

      if(!this._validate(attrs, options))

        return false;

 

      //  If you set id The property name is contained in the data set ,  will id Overlay to the model id attribute 

      //  This is to ensure in the custom id The property name after ,  Access model id When the attribute ,  It can also be accessed correctly id

      if(this.idAttribute in attrs)

        this.id = attrs[this.idAttribute];

 

      var changes = options.changes = {};

      // now Record the data objects in the current model 

      var now = this.attributes;

      // escaped Record the pass in the current model escape Cached data 

      var escaped = this._escapedAttributes;

      // prev Record the value of the data in the model before it is changed 

      var prev = this._previousAttributes || {};

 

      //  Iterate over the data objects that need to be set 

      for(attr in attrs) {

        // attr Stores the current property name , val Stores the value of the current property 

        val = attrs[attr];

 

        //  If the current data does not exist in the model ,  Or has changed ,  Or in the options Specified in the unset Attribute to delete ,  Deletes the data and replaces it _escapedAttributes The data in the 

        if(!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {

          //  Delete only through escape Cached data ,  This is to ensure that the data in the cache stays in sync with the real data in the model 

          delete escaped[attr];

          //  If you specify silent attribute ,  Is this set The method call does not fire change The event ,  Therefore, the changed data will be recorded _silent Properties of the ,  To facilitate the 1 Once the trigger change When an event is ,  Notification event listeners that this data has changed 

          //  If not specified silent attribute ,  Set directly changes Property for the current state of the data 

          (options.silent ? this._silent : changes)[attr] = true;

        }

 

        //  If the options Set in the unset,  Deletes the data from the model ( including key)

        //  If not specified unset attribute ,  The data will be added or modified ,  Add new data to the data object of the model 

        options.unset ?

        delete now[attr] : now[attr] = val;

 

        //  If the data in the model does not match the new data 1 to ,  Indicates that the data has changed 

        if(!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {

          //  in changed Property records the state of the current property that has changed 

          this.changed[attr] = val;

          if(!options.silent)

            this._pending[attr] = true;

        } else {

          //  If the data doesn't change ,  from changed Property to remove the changed state 

          delete this.changed[attr];

          delete this._pending[attr];

        }

      }

 

      //  call change methods ,  Will trigger change Event-bound functions 

      if(!options.silent)

        this.change(options);

      return this;

    },

    //  Removes the specified data from the current model ( Properties will also be deleted )

    unset : function(attr, options) {

      (options || ( options = {})).unset = true;

      //  through options.unset Configuration item notification set Method to delete 

      return this.set(attr, null, options);

    },

    //  Clears all data and properties in the current model 

    clear : function(options) {

      (options || ( options = {})).unset = true;

      //  cloning 1 Three copies of the properties of the current model ,  And through the options.unset Configuration item notification set Method performs a delete operation 

      return this.set(_.clone(this.attributes), options);

    },

    //  Get the default model data from the server ,  Use after getting the data set Method to populate the model with data ,  So if the data you get is not the same as the data in the current model 1 to ,  Would trigger change The event 

    fetch : function(options) {

      //  Make sure that options is 1 A new object ,  And then that will change options The properties in the 

      options = options ? _.clone(options) : {};

      var model = this;

      //  in options You can specify a custom callback function after the data is obtained successfully 

      var success = options.success;

      //  When the data is obtained successfully, populate the data and call the custom success callback function 

      options.success = function(resp, status, xhr) {

        //  through parse Method converts the data returned by the server 

        //  through set Method to populate the model with the transformed data ,  So it might trigger change The event ( When the data changes )

        //  If validation fails while populating the data ,  The custom is not invoked success The callback function 

        if(!model.set(model.parse(resp, xhr), options))

          return false;

        //  Call custom success The callback function 

        if(success)

          success(model, resp);

      };

      //  Pass when the request has an error wrapError To deal with error The event 

      options.error = Backbone.wrapError(options.error, model, options);

      //  call sync Method to retrieve data from the server 

      return (this.sync || Backbone.sync).call(this, 'read', this, options);

    },

    //  Save the data from the model to the server 

    save : function(key, value, options) {

      // attrs Stores data objects that need to be saved to the server 

      var attrs, current;

 

      //  Supports ways to set individual properties  key: value

      //  Supports batch setup in object form  {key: value}

      if(_.isObject(key) || key == null) {

        //  if key is 1 An object ,  It is thought to be set by means of objects 

        //  At this time the first 2 The parameters are considered to be options

        attrs = key;

        options = value;

      } else {

        //  If it's through key: value Form sets a single property ,  Set directly attrs

        attrs = {};

        attrs[key] = value;

      }

      //  The configuration object must be 1 A new object 

      options = options ? _.clone(options) : {};

 

      //  If the options Set in the wait options ,  The changed data will be validated in advance ,  And the server is not responding to new data ( Or the response fails ) when ,  Local data is restored to its pre-modified state 

      //  If it's not set wait options ,  Whether the server was set up successfully or not ,  Local data will be modified to the latest state 

      if(options.wait) {

        //  Verify ahead of time the data that needs to be saved 

        if(!this._validate(attrs, options))

          return false;

        //  Record the data in the current model ,  Used after sending data to the server ,  Restore the data 

        //  If the server fails to respond or returns no data ,  The state before modification can be maintained 

        current = _.clone(this.attributes);

      }

 

      // silentOptions in options Object silent( The data is not validated )

      //  When using wait When using parameters silentOptions Configuration items ,  Because the data has been validated above 

      //  If it's not set wait parameter ,  The original is still used options Configuration items 

      var silentOptions = _.extend({}, options, {

        silent : true

      });

      //  Save the updated data to the model ,  To facilitate the sync Method to get the model data to save to the server 

      if(attrs && !this.set(attrs, options.wait ? silentOptions : options)) {

        return false;

      }

 

      var model = this;

      //  in options You can specify a custom callback function after successfully saving the data 

      var success = options.success;

      //  Execute after server response is successful success

      options.success = function(resp, status, xhr) {

        //  Gets the latest status of the server response 

        var serverAttrs = model.parse(resp, xhr);

        //  If you use wait parameter ,  The modified data state is first set directly to the model 

        if(options.wait) {

          delete options.wait;

          serverAttrs = _.extend(attrs || {}, serverAttrs);

        }

        //  Set the latest data state to the model 

        //  If the call set Validation failed at method time ,  The custom one will not be called success The callback function 

        if(!model.set(serverAttrs, options))

          return false;

        if(success) {

          //  Custom after successful invocation response success The callback function 

          success(model, resp);

        } else {

          //  If custom callbacks are not specified ,  Is triggered by default sync The event 

          model.trigger('sync', model, resp, options);

        }

      };

      //  Pass when the request has an error wrapError To deal with error The event 

      options.error = Backbone.wrapError(options.error, model, options);

      //  Save the data from the model to the server 

      //  If the current model is 1 I'm going to create a new model ( There is no id),  Use the create methods ( new ),  Otherwise it's considered update methods ( Modify the )

      var method = this.isNew() ? 'create' : 'update';

      var xhr = (this.sync || Backbone.sync).call(this, method, this, options);

      //  If it's set options.wait,  The data is restored to its pre-modified state 

      //  The saved request has not yet been responded to ,  So if the response fails ,  The model will remain as it was before the modification ,  If the server responds successfully ,  Will be in success , sets the data in the model to the latest state 

      if(options.wait)

        this.set(current, silentOptions);

      return xhr;

    },

    //  Remove the model ,  The model will also be derived from the owner Collection Collection has been deleted 

    //  If the model is created on the client side ,  It is removed directly from the client 

    //  If the model data also exists on the server ,  The data on the server side is also deleted 

    destroy : function(options) {

      //  The configuration item must be 1 A new object 

      options = options ? _.clone(options) : {};

      var model = this;

      //  in options You can specify a custom callback function after a successful deletion of data 

      var success = options.success;

      //  Data deletion was successfully invoked ,  The trigger destroy The event ,  If the model exists in Collection In the collection ,  The collection will listen destroy Event and removes the model from the collection when triggered 

      //  When the model is deleted ,  The data in the model has not been emptied ,  But the model has been removed from the collection ,  So when there is no reference anywhere to the model ,  Will be automatically released from memory 

      //  It is recommended that you delete the model ,  Set the model object's reference variable to null

      var triggerDestroy = function() {

        model.trigger('destroy', model, model.collection, options);

      };

      //  If the model is 1 A new model for the client ,  Call directly triggerDestroy Remove the model from the collection 

      if(this.isNew()) {

        triggerDestroy();

        return false;

      }

 

      //  When deleting data from the server succeeds 

      options.success = function(resp) {

        //  If the options Configuration in object wait item ,  Represents model data in local memory ,  Will be deleted after the server data was successfully deleted 

        //  If the server fails to respond ,  The local data will not be deleted 

        if(options.wait)

          triggerDestroy();

        if(success) {

          //  Calls the custom successful callback function 

          success(model, resp);

        } else {

          //  If there is no custom callback ,  Is triggered by default sync The event 

          model.trigger('sync', model, resp, options);

        }

      };

      //  Pass when the request has an error wrapError To deal with error The event 

      options.error = Backbone.wrapError(options.error, model, options);

      //  through sync Method sends a request to delete data 

      var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);

      //  If there is no options Configuration in object wait item ,  The local data is deleted first ,  The request is then sent to delete the server data 

      //  This is regardless of whether the server deletion was successful ,  The local model data has been deleted 

      if(!options.wait)

        triggerDestroy();

      return xhr;

    },

    //  Gets the corresponding model in the server interface url,  In the call save, fetch, destroy And other ways to interact with the server ,  This method will be used to get url

    //  The generated url Similar to the "PATHINFO" model ,  The server operates only on the model 1 a url,  For modify and delete operations will be in url Postappend model id Easy to identify 

    //  If it's defined in the model urlRoot,  The server interface should be [urlRoot/id] In the form of 

    //  If the model belongs to Collection The set defines url Method or property ,  Is used in the collection url In the form of : [collection.url/id]

    //  On access server url When in url The model is appended to it id,  Facilitate server identification 1 records ,  So in the model id Needs to correspond to server records 

    //  If the model or collection cannot be obtained url,  Will call urlError Method to throw a 1 An abnormal 

    //  If the server interface is not followed "PATHINFO" Way to organize ,  You can reload url Method to achieve seamless interaction with the server 

    url : function() {

      //  Define the corresponding server url The path 

      var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();

      //  If the current model is a new model created by the client ,  There are no id attribute ,  The server url Direct use of base

      if(this.isNew())

        return base;

      //  If the current model has id attribute ,  Maybe it's called save or destroy methods ,  Will be in base The model is appended id

      //  Next we will determine base The last 1 Is a character "/",  The generated url Format for [base/id]

      return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);

    },

    // parse Method is used to parse the data fetched from the server ,  return 1 A can be set Method To parse the model data 

    // 1 a parse Methods are overloaded based on the data returned by the server ,  To build a seamless connection to the server 

    //  When the server returns the data structure with set Method does not require a data structure 1 to ( For example, server returns XML Format data time ),  You can use parse Method transformation 

    parse : function(resp, xhr) {

      return resp;

    },

    //  create 1 A new model ,  It has the same data as the current model 

    clone : function() {

      return new this.constructor(this.attributes);

    },

    //  Check if the current model is a new model created by the client 

    //  The check is based on whether the model exists id logo ,  The new model created by the client does not id logo 

    //  Therefore, the model data that the server responds to must be included id logo ,  The attribute name of the identity defaults to "id",  It can also be modified idAttribute Property to customize the identity 

    isNew : function() {

      return this.id == null;

    },

    //  Triggered when data is updated change Event-bound functions 

    //  when set Method is called ,  Will automatically call change methods ,  If the set Specified when the method is called silent configuration ,  You need to call it manually change methods 

    change : function(options) {

      // options It must be 1 An object 

      options || ( options = {});

      // this._changing There are some problems with the logic 

      // this._changing At the end of the method is set to false,  So above the method changing The value of the variable is always zero false( The first 1 Time for undefined)

      //  The author's original intention should be to use this variable change Method has completed execution ,  It doesn't make sense for a single-threaded script on the browser side ,  Because this method will block other scripts when executed 

      // changing To get on 1 The state of the second execution ,  If the 1 The script did not complete execution ,  The value of true

      var changing = this._changing;

      //  Start execution identification ,  The value is always zero during execution true,  After execution this._changing Be modified to false

      this._changing = true;

 

      //  Adds a data state that is not changed this time to _pending In the object 

      for(var attr in this._silent)

      this._pending[attr] = true;

 

      // changes Object contains the current data 1 Time to perform change Events so far ,  All data that has been changed 

      //  If you used it before silent Not trigger change The event ,  I'm going to put it in changes In the object 

      var changes = _.extend({}, options.changes, this._silent);

      //  reset _silent object 

      this._silent = {};

      //  traverse changes object ,  For each 1 Attributes trigger individual attributes change The event 

      for(var attr in changes) {

        //  will Model object ,  Attribute values ,  The listener function to which the configuration item is passed as a parameter to the event 

        this.trigger('change:' + attr, this, this.get(attr), options);

      }

 

      //  If the method is in execution ,  Stop execution 

      if(changing)

        return this;

 

      //  The trigger change The event ,  After any data has been changed ,  Will trigger in turn "change: attribute " Events and "change" The event 

      while(!_.isEmpty(this._pending)) {

        this._pending = {};

        //  The trigger change The event ,  And will be Model Instances and configuration items are passed as parameters to the listener function 

        this.trigger('change', this, options);

        //  traverse changed Data in an object ,  In turn, the state of the changed data is changed from changed Remove the 

        //  If called after that hasChanged Check data status ,  Will get false( Did not change )

        for(var attr in this.changed) {

          if(this._pending[attr] || this._silent[attr])

            continue;

          //  remove changed The state of the data in 

          delete this.changed[attr];

        }

        // change Event execution complete , _previousAttributes Property records the most recent copy of the data for the current model 

        //  So if you want to get on the data 1 A state of , 1 General only passes in the triggered change Pass through the event previous or previousAttributes Methods to obtain 

        this._previousAttributes = _.clone(this.attributes);

      }

 

      //  Completion mark 

      this._changing = false;

      return this;

    },

    //  Check to see if some data is on 1 Time to perform change It was changed after the event 

    /**

     * 1 As in the change Event coordination previous or previousAttributes Methods using ,  Such as :

     * if(model.hasChanged('attr')) {

     *   var attrPrev = model.previous('attr');

     * }

     */

    hasChanged : function(attr) {

      if(!arguments.length)

        return !_.isEmpty(this.changed);

      return _.has(this.changed, attr);

    },

    //  Gets the data in the current model and above 1 A set of data that has changed in the secondary data 

    // (1 As in the use of silent Property is not called change methods ,  So the data will be held temporarily changed Properties of the ,  on 1 Secondary data can be passed previousAttributes Methods to obtain )

    //  If passed on diff A collection of ,  Will be used on 1 Submodel data and diff The data in the collection is compared ,  Return not 1 To a data set 

    //  If there is no difference in the comparison results ,  It returns false

    changedAttributes : function(diff) {

      //  If not specified diff,  Returns the current model above 1 A collection of data whose substate has changed ,  That data already exists changed Properties of the ,  Therefore return changed A collection of 1 A copy of the 

      if(!diff)

        return this.hasChanged() ? _.clone(this.changed) : false;

      //  Specifies what you want to compare diff A collection of ,  Will return on 1 Secondary data and diff Set comparison results 

      // old The variable is stored on 1 Model data for each state 

      var val, changed = false, old = this._previousAttributes;

      //  traverse diff A collection of ,  And every 1 On the items with 1 A set of states for comparison 

      for(var attr in diff) {

        //  Will compare the results not 1 To temporarily store the data to changed variable 

        if(_.isEqual(old[attr], ( val = diff[attr])))

          continue;

        (changed || (changed = {}))[attr] = val;

      }

      //  Return comparison results 

      return changed;

    },

    //  Triggered in the model change In the event ,  Gets an attribute before it is changed 1 The data of the states , 1 Generally used for data comparison or rollback 

    //  The method 1 As in the change Call in event , change After the event is triggered , _previousAttributes Property holds the most recent data 

    previous : function(attr) {

      // attr Specify the need to get on 1 Attribute names for states 

      if(!arguments.length || !this._previousAttributes)

        return null;

      return this._previousAttributes[attr];

    },

    //  Trigger in model change In the event ,  Gets all properties 1 A collection of data of states 

    //  The method is similar to previous() methods , 1 As in the change Call in event ,  Used for data comparison or rollback 

    previousAttributes : function() {

      //  Will be on 1 The data object of the state is cloned as 1 Five new objects and return 

      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.

    //  Verify that the data in the current model passes validate Methods validation ,  Make sure it is defined before invoking it validate methods 

    isValid : function() {

      return !this.validate(this.attributes);

    },

    //  Data validation method ,  In the call set, save, add Wait for data to update the method ,  Be executed automatically 

    //  Failure to verify will trigger the model object "error" The event ,  If the options Specified in the error The processing function ,  Will only be executed options.error function 

    // @param {Object} attrs  Data model attributes attribute ,  Store the objectified data for the model 

    // @param {Object} options  Configuration items 

    // @return {Boolean}  Verify through return true,  Do not pass back false

    _validate : function(attrs, options) {

      //  If you are calling set, save, add When the data update method is set options.silent attribute ,  Ignore validation 

      //  if Model Not added in validate methods ,  Ignore validation 

      if(options.silent || !this.validate)

        return true;

      //  Gets all property values in the object ,  And in the validate Method to verify 

      // validate Method contains 2 A parameter ,  Are data sets and configuration objects in the model respectively ,  If the validation passes, no data is returned ( The default is undefined),  Failure to verify returns data with an error message 

      attrs = _.extend({}, this.attributes, attrs);

      var error = this.validate(attrs, options);

      //  Verification by 

      if(!error)

        return true;

      //  Failed validation 

      //  If it is set in the configuration object error Error handling ,  The method is called and error data and configuration objects are passed to the method 

      if(options && options.error) {

        options.error(this, error, options);

      } else {

        //  If the model is bound error Event listeners ,  Triggers the binding event 

        this.trigger('error', this, error, options);

      }

      //  Returns an authentication failed identity 

      return false;

    }

  });

 

  // Backbone.Collection  The data model set is related 

  // -------------------

 

  // Collection A collection of stored 1 A series of data models of the same class ,  It also provides methods to operate on the model 

  var Collection = Backbone.Collection = function(models, options) {

    //  A configuration object 

    options || ( options = {});

    //  Sets the model class of the collection in the configuration parameter 

    if(options.model)

      this.model = options.model;

    //  If it's set comparator attribute ,  The data in the collection will follow comparator The sorting algorithm in the method sorts ( in add Method is automatically called )

    if(options.comparator)

      this.comparator = options.comparator;

    //  Resets the internal state of the collection when instantiated ( The first 1 The call is understood to define the state )

    this._reset();

    //  Invoke the custom initialization method ,  If you need 1 As will be overloaded initialize methods 

    this.initialize.apply(this, arguments);

    //  If you specify models data ,  The call reset Method adds data to the collection 

    //  Set on the first call silent parameter ,  So it doesn't trigger "reset" The event 

    if(models)

      this.reset(models, {

        silent : true,

        parse : options.parse

      });

  };

  //  through extend Method defines a collection class prototype method 

  _.extend(Collection.prototype, Events, {

 

    //  The model class that defines the collection ,  The model class must be 1 a Backbone.Model A subclass of 

    //  Collection dependent methods are used ( Such as add, create Etc. ) when ,  Allows passing in data objects ,  The collection method automatically creates the corresponding instance based on the defined model class 

    //  The data models stored in the collection should all be the same 1 An instance of a model class 

    model : Model,

 

    //  Initialization method ,  This method is called automatically after the collection instance is created 

    // 1 This method is overridden when a collection class is defined 

    initialize : function() {

    },

    //  return 1 An array ,  Contains data objects for each model in the collection 

    toJSON : function(options) {

      //  through Undersocre the map Method will be used in each of the sets 1 A model of toJSON The results of 1 An array ,  And return 

      return this.map(function(model) {

        //  Call each model object in turn toJSON methods ,  This method returns the data object of the model by default ( Duplicate copy )

        //  If you want to return a string or something like that ,  Can be overloaded toJSON methods 

        return model.toJSON(options);

      });

    },

    //  Add to the collection 1 One or more model objects 

    //  It will trigger by default "add" The event ,  If the options Set in the silent attribute ,  This event trigger can be turned off 

    //  The incoming models Can be 1 or 1 A series of model objects (Model Instances of the class ),  If it's set in the collection model attribute ,  Allows the data object to be passed in directly ( Such as  {name: 'test'}),  Data objects are automatically instantiated model Point to the model object 

    add : function(models, options) {

      //  Local variable definition 

      var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];

      options || ( options = {});

      // models It must be 1 An array ,  If only passed in 1 A model ,  Converts it to an array 

      models = _.isArray(models) ? models.slice() : [models];

 

      //  Iterate through the list of models that need to be added ,  During the traversal ,  The following actions will be performed :

      // -  Transform data objects into model objects 

      // -  Establish a reference between the model and the collection 

      // -  Record invalid and duplicate models ,  And filter it in the back 

      for( i = 0, length = models.length; i < length; i++) {

        //  Convert data objects to model objects ,  References to resume models and collections ,  And stored in the model( At the same time models The corresponding model has been replaced with the model object )

        if(!( model = models[i] = this._prepareModel(models[i], options))) {

          throw new Error("Can't add an invalid model to a collection");

        }

        //  Current model cid and id

        cid = model.cid;

        id = model.id;

        // dups Invalid or duplicate model indexes are recorded in the array (models Index in array ),  And the next 1 Step to filter and delete 

        //  if cids, ids The model's index already exists in the variable ,  They think it's the same 1 Two models are passed in models The array is declared multiple times 

        //  if _byCid, _byId An index of the model already exists in the object ,  Is that with 1 Five models already exist in the current set 

        //  In both cases ,  Record the index of the model to dups Filter and delete 

        if(cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {

          dups.push(i);

          continue;

        }

        //  will models Record the models that have been traversed in ,  For the next 1 Repeat the check on the secondary loop 

        cids[cid] = ids[id] = model;

      }

 

      //  from models Delete invalid or duplicate models from ,  Keep the list of models that really need to be added to the current collection 

      i = dups.length;

      while(i--) {

        models.splice(dups[i], 1);

      }

 

      //  Iterate through the models you need to add ,  Listen for model events and record them _byCid, _byId The list of ,  Used in a call get and getByCid Method as an index 

      for( i = 0, length = models.length; i < length; i++) {

        //  Listen for all events in the model ,  And perform _onModelEvent methods 

        // _onModelEvent Method will be thrown at the model add, remove, destroy and change Event processing ,  So that the model is in sync with the state in the collection 

        ( model = models[i]).on('all', this._onModelEvent, this);

        //  Base the model on cid recorded _byCid object ,  Facilitate according to cid To look up 

        this._byCid[model.cid] = model;

        //  Base the model on id recorded _byId object ,  Facilitate according to id To look up 

        if(model.id != null)

          this._byId[model.id] = model;

      }

 

      //  Variable set length attribute , length Property records the number of models in the current collection 

      this.length += length;

      //  Sets the location where the new model list is inserted into the collection ,  If the options Set in the at parameter ,  It's in the set at Position insert 

      //  The default is to insert to the end of the collection 

      //  If it's set comparator Custom sorting methods ,  Is set at After will also follow comparator Method to sort ,  So the final order may not be at Specified location 

      index = options.at != null ? options.at : this.models.length;

      splice.apply(this.models, [index, 0].concat(models));

      //  If it's set comparator methods ,  According to the data comparator Algorithm to sort 

      //  Automatic sort use silent Property prevents triggering reset The event 

      if(this.comparator)

        this.sort({

          silent : true

        });

      //  Fires on each model object in turn "add" The event ,  If it's set silent attribute ,  Prevents the event from being triggered 

      if(options.silent)

        return this;

      //  Iterate through the list of newly added models 

      for( i = 0, length = this.models.length; i < length; i++) {

        if(!cids[( model = this.models[i]).cid])

          continue;

        options.index = i;

        //  Triggering model "add" The event ,  Because the collection listens for the model "all" The event ,  So in _onModelEvent In the method ,  The collection will also fire "add" The event 

        //  Details are available Collection.prototype._onModelEvent methods 

        model.trigger('add', model, this, options);

      }

      return this;

    },

    //  Removes model objects from the collection ( Support for removing multiple models )

    //  The incoming models It could be a model object that needs to be removed ,  Or model cid And the model of id

    //  Removing the model does not invoke the model destroy methods 

    //  If it's not set options.silent parameter ,  Will trigger the model remove The event ,  Will also trigger the set remove The event ( Set by _onModelEvent Method listens for all events in the model )

    remove : function(models, options) {

      var i, l, index, model;

      // options Default is an empty object 

      options || ( options = {});

      // models Must be of array type ,  When only remove 1 A model ,  Put it in the 1 An array 

      models = _.isArray(models) ? models.slice() : [models];

      //  Iterate through the list of models that need to be removed 

      for( i = 0, l = models.length; i < l; i++) {

        //  The incoming models The list can be model objects that need to be removed ,  Or model cid And the model of id

        // ( in getByCid and get In the method ,  through cid, id To get the model ,  So if I pass in 1 Model objects ,  Returns the model itself )

        model = this.getByCid(models[i]) || this.get(models[i]);

        //  No model is obtained 

        if(!model)

          continue;

        //  from _byId Remove the model from the list id reference 

        delete this._byId[model.id];

        //  from _byCid Remove the model from the list cid reference 

        delete this._byCid[model.cid];

        // indexOf is Underscore Methods in objects ,  Here by indexOf Method gets the location where the model first appears in the collection 

        index = this.indexOf(model);

        //  Removes the model from the collection list 

        this.models.splice(index, 1);

        //  Reset the current collection length attribute ( Record the number of models in the collection )

        this.length--;

        //  If it's not set silent attribute ,  Triggers the model remove The event 

        if(!options.silent) {

          //  Adds the location of the current model in the collection to options Object and pass to remove Listen for an event ,  So that it can be used in event functions 

          options.index = index;

          model.trigger('remove', model, this, options);

        }

        //  Detach the model from the set ,  Includes references to models and event listeners in the collection 

        this._removeReference(model);

      }

      return this;

    },

    //  Adds a model object to the end of the collection 

    //  If it's defined in the collection class comparator Sorting method ,  by push Method to add the model as follows comparator Define the algorithm to sort ,  So the order of the models may change 

    push : function(model, options) {

      //  through _prepareModel Method will be model Instantiate model objects ,  This code is redundant ,  Because it's called down here add Method will also pass _prepareModel To obtain 1 Time model 

      model = this._prepareModel(model, options);

      //  call add Method adds the model to the collection ( Adds to the end of the collection by default )

      this.add(model, options);

      return model;

    },

    //  Removes the last part of the set 1 Model objects 

    pop : function(options) {

      //  Gets the last in the collection 1 A model 

      var model = this.at(this.length - 1);

      //  through remove Method to remove the model 

      this.remove(model, options);

      return model;

    },

    //  To the set 1 Four positions are inserted into the model 

    //  If it's defined in the collection class comparator Sorting method ,  by unshift Method to add the model as follows comparator Define the algorithm to sort ,  So the order of the models may change 

    unshift : function(model, options) {

      //  through _prepareModel Method will be model Instantiate model objects 

      model = this._prepareModel(model, options);

      //  call add Method inserts the model to the end of the collection 1 A position ( Set up the at for 0)

      //  If it's defined comparator Sorting method ,  The order of the sets will be rearranged 

      this.add(model, _.extend({

        at : 0

      }, options));

      return model;

    },

    //  Removes and returns the table in the collection 1 Model objects 

    shift : function(options) {

      //  Gets the order in the set 1 A model 

      var model = this.at(0);

      //  Delete the model from the collection 

      this.remove(model, options);

      //  Return model object 

      return model;

    },

    //  According to the id Find the model from the collection and return it 

    get : function(id) {

      if(id == null)

        return

        void 0;

      return this._byId[id.id != null ? id.id : id];

    },

    //  According to the cid Find the model from the collection and return it 

    getByCid : function(cid) {

      return cid && this._byCid[cid.cid || cid];

    },

    //  According to the index ( The subscript ,  from 0 start ) Find the model from the collection and return it 

    at : function(index) {

      return this.models[index];

    },

    //  The models in the collection are filtered by value 

    // attrs is 1 10 filter objects ,  Such as  {name: 'Jack'},  Returns all of the values in the collection name for "Jack" The model ( An array of )

    where : function(attrs) {

      // attrs Cannot be null 

      if(_.isEmpty(attrs))

        return [];

      //  through filter Method to filter the models in the collection 

      // filter Method is Underscore The methods in ,  Used to iterate over elements in a collection ,  And will be able to pass processor validation ( The return value is true) Is returned as an array 

      return this.filter(function(model) {

        //  traverse attrs Validation rules in an object 

        for(var key in attrs) {

          //  will attrs The validation rules in the collection match the models in the collection 

          if(attrs[key] !== model.get(key))

            return false;

        }

        return true;

      });

    },

    //  Follow the model in the set comparator Property specifies the method to sort 

    //  If there is no options Set in the silent parameter ,  Then the sort will trigger reset The event 

    sort : function(options) {

      // options The default is 1 An object 

      options || ( options = {});

      //  call sort Method must be specified comparator attribute ( Sorting algorithm method ),  Otherwise it will be thrown 1 A mistake 

      if(!this.comparator)

        throw new Error('Cannot sort a set without a comparator');

      // boundComparator Stored bound to the current collection context object comparator Sorting algorithm method 

      var boundComparator = _.bind(this.comparator, this);

      if(this.comparator.length == 1) {

        this.models = this.sortBy(boundComparator);

      } else {

        //  call Array.prototype.sort through comparator The algorithm performs a custom sort on the data 

        this.models.sort(boundComparator);

      }

      //  If not specified silent parameter ,  The trigger reset The event 

      if(!options.silent)

        this.trigger('reset', this, options);

      return this;

    },

    //  Will set the values of all models attr Attribute values are stored in 1 Array and returns 

    pluck : function(attr) {

      // map is Underscore The methods in ,  Used to traverse the 1 A collection of ,  And the return value of all processors as 1 Number of arrays returned 

      return _.map(this.models, function(model) {

        //  Returns the values of the current model attr Attribute values 

        return model.get(attr);

      });

    },

    //  Replace all model data in the collection (models)

    //  This action deletes all the current data and state in the collection ,  And reset the data to models

    // models It should be 1 An array ,  Can contain 1 A series of Model The model object ,  Or the original object ( Will be in add Method is automatically created as a model object )

    reset : function(models, options) {

      // models It's the model that makes the substitution ( Or data ) An array of 

      models || ( models = []);

      // options The default is 1 An empty object 

      options || ( options = {});

      //  Traversal the models in the current collection ,  In turn, they are deleted and unreferenced from the collection 

      for(var i = 0, l = this.models.length; i < l; i++) {

        this._removeReference(this.models[i]);

      }

      //  Deletes the collection data and resets the state 

      this._reset();

      //  through add Method adds new model data to the collection 

      //  Here by exnted Method
                

Related articles: