SoFunction
Updated on 2025-04-12

0.9.2 Source code comments Chinese translation version

0.9.2 Source code comments Chinese translation version

Updated: June 25, 2015 09:11:19 Submission: junjie
Provides models, collections, and views for complex WEB applications. The model is used to bind key-value data and custom events; the collection is accompanied by a rich API of enumerable functions; the view can declare event handling functions and connect to the application through the RESRful JSON interface.
//  0.9.2

 

// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.

// Backbone may be freely distributed under the MIT license.

// For all details and documentation:

// 

(function() {

 

  // Create a global object, represented as a window object in the browser, and global object in it
  var root = this;

 

  // Save the value before the "Backbone" variable is overwritten
  // If a naming conflict occurs or the specification is taken into account, the value before the variable is occupied by Backbone can be restored through the() method and return the Backbone object for renaming.
  var previousBackbone = ;

 

  // Cache the slice and splice methods in the local variables for call
  var slice = ;

  var splice = ;

 

  var Backbone;

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

    Backbone = exports;

  } else {

    Backbone =  = {};

  }

 

  // Define Backbone version
   = '0.9.2';

 

  // Automatically import Underscore in server environment, some methods depend on or inherit from Underscore in Backbone
  var _ = root._;

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

    _ = require('underscore');

 

  // Define the third-party library as a unified variable "$", which is used to call methods in the library when view (View), event processing and synchronization with server data (sync)
  // Supported libraries include jQuery, Zepto, etc. They have the same syntax, but Zepto is more suitable for mobile development, and it is mainly aimed at the Webkit kernel browser
  // You can also customize a custom library similar to jQuery syntax for Backbone (sometimes we may need a lighter custom version than jQuery and Zepto)
  // The "$" defined here is a local variable, so it will not affect the normal use of third-party libraries outside the Backbone framework
  var $ =  ||  || ;

 

  // Manually set up third-party libraries
  // If you do not import the third-party library before importing Backbone, you can set the "$" local variable through the setDomLibrary method
  // setDomLibrary method is also commonly used to dynamically import custom libraries in Backbone
   = function(lib) {

    $ = lib;

  };

  // Abandon the name frame with "Backbone" and return the Backbone object. It is generally used to avoid naming conflicts or standardize naming methods.
  // For example:
  // var bk = (); // Cancel the "Backbone" name and store the Backbone object in the bk variable
  // (Backbone); // This variable can no longer access the Backbone object, but is restored to the value before the Backbone definition
  // var MyBackbone = bk; // And bk stores Backbone object, we rename it to MyBackbone
   = function() {

     = previousBackbone;

    return this;

  };

  // For browsers that do not support REST mode, you can set = true
  // The request with the server will be sent in POST mode, and the _method parameter is added to the data to identify the operation name, and the X-HTTP-Method-Override header information will also be sent
   = false;

 

  // For browsers that do not support application/json encoding, you can set = true;
  // Set the request type to application/x-www-form-urlencoded, and place the data in the model parameters to achieve compatibility
   = false;

 

  // Custom event related
  // -----------------

 

  // eventSplitter specifies the resolution rules for event names when handling multiple events
  var eventSplitter = /\s+/;

 

  // Custom event manager
  // allows adding, deleting and triggering custom events to objects by binding Events-related methods in objects
  var Events =  = {

 

    // Bind custom events and callback functions to the current object
    // The context object in the callback function is the specified context. If the context is not set, the context object defaults to the object of the current bound event
    // This method is similar to the addEventListener method in DOM Level2
    // events allow specifying multiple event names, separated by whitespace characters (such as spaces, tabs, etc.)
    // When the event name is "all", when the trigger method is called to trigger any event, all callback functions bound to the "all" event will be called
    on : function(events, callback, context) {

      // Define local variables used in some functions
      var calls, event, node, tail, list;

      // The callback callback function must be set
      if(!callback)

        return this;

      // parse event names through eventSplitter, split multiple event names into an array using split
      // Generally, use whitespace characters to specify multiple event names
      events = (eventSplitter);

      // calls record the bound events and callback functions list in the current object
      calls = this._callbacks || (this._callbacks = {});

 

      // Loop the event name list, store the event name in the event variable from beginning to end
      while( event = ()) {

        // Get the callback function that has been bound to the event event
        // list stores the callback callback function list bound in a single event name
        // The function list is not stored in an array, but is associated in sequence through the next attributes of multiple objects
        /** Data formats are as follows:

          * {

          * tail: {Object},

          * next: {

          * callback: {Function},

          * context: {Object},

          * next: {

          * callback: {Function},

          * context: {Object},

          * next: {Object}

          * }

          * }

          * }

          */

        // Each next object on the list stores information about a callback event (function body, context and next callback event)
        // The top level of the event list stores a tail object, which stores the identification of the last bound callback event (the next pointing to the same object as the last callback event)
        // Through tail identification, you can know that the last callback function has been reached when traversing the callback list
        list = calls[event];

        // The node variable is used to record the relevant information of this callback function
        // tail only stores the last bound callback function's identification
        // Therefore, if the callback function has been bound before, then assign the previous tail to node as an object, and then create a new object to tail
        // The reason why this callback event is added to the tail object of the last callback is to allow the object hierarchy relationship of the callback function list to be arranged in the order of binding (the latest bound events will be placed at the bottom)
        node = list ?  : {};

         = tail = {};

        // Record the function body and context information of this callback
         = context;

         = callback;

        // Reassemble the callback list of the current event, the callback event has been added to the list
        calls[event] = {

          tail : tail,

          next : list ?  : node

        };

      }

      // Return to the current object, convenient for method chain call
      return this;

    },

    // Remove bound events or callback functions in the object. You can filter events or callback functions that need to be deleted through events, callbacks and context.
    // - If the context is empty, remove all functions specified by callback
    // - If callback is empty, remove all callback functions in the event
    // - If events are empty, but callback or context is specified, remove the callback function specified by callback or context (not distinguishing between event names)
    // - If no parameters are passed, all events and callback functions bound in the object are removed
    off : function(events, callback, context) {

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

 

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

      // The current object does not have any events bound to
      if(!( calls = this._callbacks))

        return;

      // If no parameters are specified, all events and callback functions are removed (remove the _callbacks attribute)
      if(!(events || callback || context)) {

        delete this._callbacks;

        return this;

      }

 

      // parse the list of events that need to be removed
      // - If events are specified, parse the event name according to eventSplitter
      // - If events are not specified, resolve the name list of all events that have been bound
      events = events ? (eventSplitter) : _.keys(calls);

 

      // List of loop event names
      while( event = ()) {

        // Remove the current event object from the list and cache it into the node variable
        node = calls[event];

        delete calls[event];

        // If the current event object does not exist (or no removal filter conditions are specified, it is considered that the current event and all callback functions will be removed), and the operation will be terminated (the event object has been removed in the previous step)
        if(!node || !(callback || context))

          continue;

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

        // Assemble a new event object and rebind it according to the callback function or context filtering conditions
        tail = ;

        // traverse all callback objects in the event
        while(( node = ) !== tail) {

          cb = ;

          ctx = ;

          //Filter the callback function according to the callback function and context in the parameters, and rebind the callback function that does not meet the filtering conditions into the event (because all callback functions in the event have been removed above)
          if((callback && cb !== callback) || (context && ctx !== context)) {

            (event, cb, ctx);

          }

        }

      }

 

      return this;

    },

    // Trigger one or more events that have been defined, and execute the bound callback function list in turn
    trigger : function(events) {

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

      // The current object does not have any events bound to
      if(!( calls = this._callbacks))

        return this;

      // Get the "all" event list bound to the callback function list
      all = ;

      // Resolve the event name that needs to be triggered into an array according to the eventSplitter rule
      events = (eventSplitter);

      // Record trigger from the second parameter to the rest variable and pass it to the callback function in turn
      rest = (arguments, 1);

 

      // List of events that need to be triggered by loop
      while( event = ()) {

        // The node variable here records the list of all callback functions for the current event
        if( node = calls[event]) {

          // tail variable records the object identifier of the last bound event
          tail = ;

          // The value of the node variable is assigned to the bound single callback event object in sequence according to the binding order of the event.
          // The next attribute of the event bound for the last time refers to the same object as tail, using this as the basis for judging whether it reaches the end of the list.
          while(( node = ) !== tail) {

            // Execute all bound events and pass the parameters when calling trigger to the callback function
            ( || this, rest);

          }

        }

        // The variable all records the "all" event when binding, that is, when any event is called, the callback function in the "all" event will be executed
        // - The callback functions in the "all" event will be executed one after the callback function list of the current event is executed, regardless of the binding order.
        // - The "all" event should be automatically called when a normal event is triggered. If the "all" event is forced to trigger, the callback function in the event will be executed twice
        if( node = all) {

          tail = ;

          // The difference with the callback function that calls ordinary events is that the all event will pass the currently called event name to the callback function as the first parameter
          args = [event].concat(rest);

          // traverse and execute the callback function list in the "all" event
          while(( node = ) !== tail) {

            ( || this, args);

          }

        }

      }

 

      return this;

    }

  };

 

  // Alias ​​for binding events and releasing events are also compatible with previous versions of Backbone
   = ;

   = ;

 

  // Data object model
  // --------------

 

  // Model is the base class of all data object models in Backbone, used to create a data model
  // @param {Object} attributes Specify the initialization data when creating the model
  // @param {Object} options

  /**

   * @format options

   * {

   *   parse: {Boolean},

   *   collection: {Collection}

   * }

   */

  var Model =  = function(attributes, options) {

    // The defaults variable is used to store the default data of the model
    var defaults;

    // If the attributes parameter is not specified, set attributes to an empty object
    attributes || ( attributes = {});

    // Set the default data parsing method for attributes, for example, the default data is obtained from the server (or the original data is in XML format). In order to be compatible with the data format required by the set method, the parse method can be used for parsing.
    if(options && )

      attributes = (attributes);

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

      // If the Model sets default data when defining, the initialization data uses the data merged with the attributes parameter (the data in attributes will overwrite the data of the same name in defaults)
      attributes = _.extend({}, defaults, attributes);

    }

    // explicitly specify the Collection object to which the model belongs (when calling the add, push, etc. of the Collection method to add the model to the collection, the Collection object to which the model belongs will be automatically set)
    if(options && )

       = ;

    // The attributes attributes store the JSON objectized data of the current model, and the default is empty when creating the model.
     = {};

    // Define the _escapedAttributes cache object, which will cache the data processed through the escape method
    this._escapedAttributes = {};

    // Configure a unique identifier for each model
     = _.uniqueId('c');

    // Define a series of objects used to record data status. For the specific meaning, please refer to the comments when defining the object.
     = {};

    this._silent = {};

    this._pending = {};

    // Set initialization data when creating an instance, use the silent parameter for the first time, and the change event will not be triggered.
    (attributes, {

      silent : true

    });

    // The initialization data has been set above, the state of changed, _silent, _pending objects may have changed, so the initialization is re-initialized here
     = {};

    this._silent = {};

    this._pending = {};

    // _previousAttributes variable stores a copy of the model data
    // Used to obtain the state before the model data is changed in the change event. The data of the previous state can be obtained through the previous or previousAttributes method.
    this._previousAttributes = _.clone();

    // Call initialize initialization method
    (this, arguments);

  };

  // Use the extend method to define a series of properties and methods for the Model prototype
  _.extend(, Events, {

 

    // The changed attribute records the key set of the changed data every time the set method is called
    changed : null,

 

    // // When specifying the silent attribute, the change event will not be triggered. The changed data will be recorded until the next time the change event is triggered.
    // _silent attribute is used to record the changed data when using silent
    _silent : null,

 

    _pending : null,

 

    // The unique identification attribute of each model (default is "id", you can customize the id attribute name by modifying idAttribute)
    // If the id attribute is included when setting the data, the id will overwrite the id of the model
    // id is used to find and identify models in the Collection collection. When communicating with the background interface, it will also use id as the identifier of a record.
    idAttribute : 'id',

 

    // Model initialization method is automatically called after the model is constructed
    initialize : function() {

    },

    // Return a copy of the data in the current model (JSON object format)
    toJSON : function(options) {

      return _.clone();

    },

    // Obtain the data value in the model according to the attr attribute name
    get : function(attr) {

      return [attr];

    },

    // According to the attr attribute name, get the data value in the model. The HTML special characters contained in the data value will be converted into HTML entities, including & < > " ' \
    // Implemented through the _.escape method
    escape : function(attr) {

      var html;

      // Find data from the _escapedAttributes cache object, and return it directly if the data has been cached
      if( html = this._escapedAttributes[attr])

        return html;

      // No data found in the _escapedAttributes cache object
      // Get data from the model first
      var val = (attr);

      // Convert HTML in the data to an entity using the _.escape method and cache it to the _escapedAttributes object for easy direct access next time
      return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);

    },

    // Check whether there is a certain attribute in the model. When the value of the attribute is converted to Boolean type and the value is false, it is considered to not exist
    // If the value is false, null, undefined, 0, NaN, or an empty string, it will be converted to false
    has : function(attr) {

      return (attr) != null;

    },

    // Set the data in the model. If the key value does not exist, it will be added to the model as a new attribute. If the key value already exists, it will be modified to the new value
    set : function(key, value, options) {

      // Record the data object that needs to be set in the attrs variable
      var attrs, attr, val;

 

      // Parameter form allows key-value object form, or set separately through key and value parameters
      // If the key is an object, it is considered to be set in object form, and the second parameter will be regarded as an option parameter
      if(_.isObject(key) || key == null) {

        attrs = key;

        options = value;

      } else {

        // Set the two parameters of key and value separately, put the data into the attrs object for unified processing
        attrs = {};

        attrs[key] = value;

      }

 

      // The options configuration item must be an object. If options are not set, the default value is an empty object.
      options || ( options = {});

      // No action is executed when no parameters are set
      if(!attrs)

        return this;

      // If the set data object belongs to an instance of the Model class, assign the attributes data object of the Model object to attrs
      // Generally, when copying data from a Model object to another Model object, this action will be performed
      if( attrs instanceof Model)

        attrs = ;

      // If the unset attribute is set in the options configuration object, reset all attributes in the attrs data object to undefined
      // This operation is usually performed when copying the data of one Model object to another Model object, but only needs to copy the data in the Model without copying the value.
      if()

        for(attr in attrs)

        attrs[attr] =

        void 0;

 

      // Verify the current data, stop execution if the verification fails
      if(!this._validate(attrs, options))

        return false;

 

      // If the set id attribute name is included in the data set, the id is overwritten to the model's id attribute
      // This is to ensure that after customizing the id attribute name, the id can also be accessed correctly when accessing the model's id attribute.
      if( in attrs)

         = attrs[];

 

      var changes =  = {};

      // now record the data object in the current model
      var now = ;

      // escaped records the data cached through escape in the current model
      var escaped = this._escapedAttributes;

      // Prev record the value before the data in the model is changed
      var prev = this._previousAttributes || {};

 

      // traverse the data object that needs to be set
      for(attr in attrs) {

        // attr stores the current attribute name, val stores the current attribute value
        val = attrs[attr];

 

        // If the current data does not exist in the model, or has changed, or the unset attribute is specified in options, the data that is deleted is replaced by the data in _escapedAttributes
        if(!_.isEqual(now[attr], val) || ( &amp;&amp; _.has(now, attr))) {

          // Only delete data cached through escape, this is to ensure that the cached data remains in sync with the real data in the model
          delete escaped[attr];

          // If the silent attribute is specified, the set method call will not trigger the change event, so the changed data will be recorded in the _silent attribute, so that the next time the change event is triggered, the event listening function will be notified that the event listener function has changed.
          // If the silent attribute is not specified, directly set the current data in the changes attribute to a changed state
          ( ? this._silent : changes)[attr] = true;

        }

 

        // If unset is set in options, delete the data (including key) from the model
        // If the unset attribute is not specified, it is considered that new data will be added or modified, and new data will be added to the model's data object.
         ?

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

 

        // If the data in the model does not match the new data, it means that the data has changed
        if(!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {

          // Record the status of the current property that has changed in the changed property
          [attr] = val;

          if(!)

            this._pending[attr] = true;

        } else {

          // If the data does not change, remove the changed state from the changed property
          delete [attr];

          delete this._pending[attr];

        }

      }

 

      // Call the change method to trigger the function that binds the change event
      if(!)

        (options);

      return this;

    },

    // Delete the specified data from the current model (the attributes will also be deleted at the same time)
    unset : function(attr, options) {

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

      // Delete the set method through the configuration item
      return (attr, null, options);

    },

    // Clear all data and properties in the current model
    clear : function(options) {

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

      //Clone a copy of the attributes of the current model and inform the set method to perform the delete operation through the configuration item
      return (_.clone(), options);

    },

    // Get the default model data from the server, and use the set method to fill the data into the model after obtaining the data. Therefore, if the obtained data is inconsistent with the data in the current model, the change event will be triggered
    fetch : function(options) {

      // Make sure options are a new object, and the properties in options will be changed.
      options = options ? _.clone(options) : {};

      var model = this;

      // In options, you can specify a custom callback function after the data is successfully obtained
      var success = ;

      // When the data is successfully obtained, fill the data and call the custom successful callback function
       = function(resp, status, xhr) {

        // Convert the data returned by the server through the parse method
        //Fill the converted data into the model through the set method, so the change event may be triggered (when the data changes)
        // If verification fails while filling data, the custom success callback function will not be called
        if(!((resp, xhr), options))

          return false;

        // Call custom success callback function
        if(success)

          success(model, resp);

      };

      // When an error occurs in the request, handle the error event through wrapError
       = (, model, options);

      // Call sync method to get data from the server
      return ( || ).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 setting a single attribute key: value
      // Support batch setting method in object form {key: value}
      if(_.isObject(key) || key == null) {

        // If the key is an object, it is considered to be set through object
        // At this time the second parameter is considered options
        attrs = key;

        options = value;

      } else {

        // If a single attribute is set through the key: value form, then set attrs directly
        attrs = {};

        attrs[key] = value;

      }

      // The configuration object must be a new object
      options = options ? _.clone(options) : {};

 

      // If the wait option is set in options, the changed data will be verified in advance, and if the server does not respond to new data (or fails in response), the local data will be restored to the state before modification
      // If the wait option is not set, the local data will be modified to the latest state regardless of whether the server is successfully set.
      if() {

        // Verify the data to be saved in advance
        if(!this._validate(attrs, options))

          return false;

        // Record the data in the current model, which is used to restore the data after sending the data to the server
        // If the server response fails or does not return data, it can maintain the status before modification
        current = _.clone();

      }

 

      // silentOptions adds silent to the options object (no data verification)
      // Use the silentOptions configuration item when using the wait parameter, because the data has been verified above
      // If the wait parameter is not set, the original options configuration item is still used
      var silentOptions = _.extend({}, options, {

        silent : true

      });

      // Save the modified latest data to the model to facilitate obtaining model data in the sync method and saving it to the server
      if(attrs &amp;&amp; !(attrs,  ? silentOptions : options)) {

        return false;

      }

 

      var model = this;

      // In options, you can specify a custom callback function after the data is saved successfully
      var success = ;

      // After the server response is successful, execute success
       = function(resp, status, xhr) {

        // Get the latest status data of the server response
        var serverAttrs = (resp, xhr);

        // If the wait parameter is used, the modified data state will be set directly to the model first.
        if() {

          delete ;

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

        }

        // Set the latest data status to the model
        // If the verification fails when calling the set method, the custom success callback function will not be called
        if(!(serverAttrs, options))

          return false;

        if(success) {

          // Custom success callback function after the response is successful
          success(model, resp);

        } else {

          // If no custom callback is specified, the sync event will be triggered by default
          ('sync', model, resp, options);

        }

      };

      // When an error occurs in the request, handle the error event through wrapError
       = (, model, options);

      // Save the data in the model to the server
      // If the current model is a newly created model (without id), use the create method (newly added), otherwise it is considered an update method (modified)
      var method = () ? 'create' : 'update';

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

      // If set, restore the data to the state before modification
      // The saved request has not been responded yet, so if the response fails, the model will remain in the state before modification. If the server response is successful, the data in the model will be set to the latest state in success
      if()

        (current, silentOptions);

      return xhr;

    },

    // Delete the model, the model will be deleted from the collection it belongs to at the same time
    // If the model is newly created on the client, it will be deleted directly from the client
    // If the model data exists on the server at the same time, the server-side data will be deleted at the same time
    destroy : function(options) {

      // The configuration item must be a new object
      options = options ? _.clone(options) : {};

      var model = this;

      // In options, you can specify a custom callback function after the data is deleted successfully
      var success = ;

      // Delete data successfully and trigger the destroy event. If the model exists in the Collection collection, the collection will listen for the destroy event and remove the model from the collection when triggered.
      // When deleting the model, the data in the model is not cleared, but the model has been removed from the collection. Therefore, when the model is not referenced anywhere, it will be automatically released from memory.
      // It is recommended to set the reference variable of the model object to null when deleting the model
      var triggerDestroy = function() {

        ('destroy', model, , options);

      };

      // If the model is a newly created model by the client, directly call triggerDestroy to remove the model from the collection
      if(()) {

        triggerDestroy();

        return false;

      }

 

      // When data is deleted from the server is successful
       = function(resp) {

        // If the wait item is configured in the options object, it means that the model data in local memory will be deleted after the server data is successfully deleted.
        // If the server response fails, the local data will not be deleted
        if()

          triggerDestroy();

        if(success) {

          // Call a custom successful callback function
          success(model, resp);

        } else {

          // If there is no custom callback, the sync event will be triggered by default
          ('sync', model, resp, options);

        }

      };

      // When an error occurs in the request, handle the error event through wrapError
       = (, model, options);

      // Send a request to delete data through sync method
      var xhr = ( || ).call(this, 'delete', this, options);

      // If the wait item is not configured in the options object, the local data will be deleted first, and then the server data will be deleted.
      // At this time, regardless of whether the server deletes successfully, the local model data has been deleted
      if(!)

        triggerDestroy();

      return xhr;

    },

    // Get the corresponding url of the model in the server interface. When calling save, fetch, destroy and other methods that interact with the server, this method will be used to obtain the url
    // The generated url is similar to the "PATHINFO" mode. The server has only one url for the model operation. For modification and deletion operations, the model id will be added after the url for easy identification.
    // If urlRoot is defined in the model, the server interface should be in the form [urlRoot/id]
    // If the Collection collection to which the model belongs defines a url method or property, then use the url form in the collection: [/id]
    // When accessing the server url, the model's id will be added after the url, so the server can identify a record, so the id in the model needs to correspond to the server record.
    // If the url of the model or collection cannot be obtained, the urlError method will be called and an exception will be thrown
    // If the server interface is not organized in the "PATHINFO" mode, seamless interaction with the server can be achieved by overloading the url method.
    url : function() {

      // Define the URL path corresponding to the server
      var base = getValue(this, 'urlRoot') || getValue(, 'url') || urlError();

      // If the current model is a newly created model by the client, the id attribute does not exist, and the server url uses base directly
      if(())

        return base;

      // If the current model has an id attribute, it may be that the save or destroy method is called, and the id of the model will be appended after the base
      // The following will determine whether the last character of base is "/", and the generated url format is [base/id]
      return base + (( - 1) == '/' ? '' : '/') + encodeURIComponent();

    },

    // The parse method is used to parse the data obtained from the server and return a model data that can be parsed by the set method.
    // Generally, parse method will be overloaded based on the data returned by the server to build a seamless connection with the server
    // When the data structure returned by the server is inconsistent with the data structure required by the set method (for example, when the server returns XML format data), the parse method can be used for conversion.
    parse : function(resp, xhr) {

      return resp;

    },

    // Create a new model with the same data as the current model
    clone : function() {

      return new ();

    },

    // Check whether the current model is a new model created by the client
    // The check method is to determine whether the model has an id identifier. The new model created by the client does not have an id identifier.
    // Therefore, the model data of the server response must contain the id identifier. The attribute name of the identifier is "id". You can also customize the identifier by modifying the idAttribute attribute.
    isNew : function() {

      return  == null;

    },

    // Function that triggers the change event binding when the data is updated
    // When the set method is called, the change method will be automatically called. If a silent configuration is specified when the set method is called, the change method needs to be called manually
    change : function(options) {

      // options must be an object
      options || ( options = {});

      // There are some problems with this._changing related logic
      // this._changing is set to false at the end of the method, so the value of the changing variable above the method is always false (undefined for the first time)
      // The author's original intention should be to use this variable to indicate whether the change method has been executed. It has no meaning for a single-threaded script on the browser side, because the method will block other scripts when executed
      // Change gets the status of the last execution. If the last script has not been executed, the value is true
      var changing = this._changing;

      // The execution flag is started, the value is always true during execution, and this._changing is modified to false after execution.
      this._changing = true;

 

      // Add data state that is not changed this time to the _pending object
      for(var attr in this._silent)

      this._pending[attr] = true;

 

      // The changes object contains all data that has been changed since the last change event was executed
      // If the change event is not triggered before using silent, this will be placed in the changes object
      var changes = _.extend({}, , this._silent);

      // Reset the _silent object
      this._silent = {};

      // traverse the changes object and trigger a separate change event for each property separately
      for(var attr in changes) {

        // Pass the Model object, attribute value, and configuration items as parameters to the listening function of the event
        ('change:' + attr, this, (attr), options);

      }

 

      // If the method is in execution, stop execution
      if(changing)

        return this;

 

      // Trigger the change event. After any data is changed, the "change: attribute" event and the "change" event will be triggered in turn.
      while(!_.isEmpty(this._pending)) {

        this._pending = {};

        // Trigger the change event, and pass the Model instance and configuration items as parameters to the listening function
        ('change', this, options);

        // traverse the data in the changed object and remove the changed data state from the changed one after another
        // After this, if hasChanged is called to check the data status, false will be obtained (not changed)
        for(var attr in ) {

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

            continue;

          // Remove the status of the data in changed
          delete [attr];

        }

        // The change event has been executed, and the _previousAttributes attribute will record the latest data copy of the current model
        // Therefore, if you need to obtain the previous state of the data, it is generally only obtained by the previous or previousAttributes method in the triggered change event.
        this._previousAttributes = _.clone();

      }

 

      // Execution completed logo
      this._changing = false;

      return this;

    },

    // Check whether a certain data has been changed after the last change event is executed
    /**

      * Generally used in the change event with previous or previousAttributes methods, such as:

      * if(('attr')) {

      * var attrPrev = ('attr');

      * }

      */

    hasChanged : function(attr) {

      if(!)

        return !_.isEmpty();

      return _.has(, attr);

    },

    // Get the data set in the current model and the data that has changed in the last data
    // (Generally, the change method is not called when using the silent attribute, so the data will be temporarily held in the changed attribute. The last data can be obtained through the previousAttributes method)
    // If the diff collection is passed, the last model data will be compared with the data in the diff collection, and the inconsistent data collection will be returned
    // If there is no difference in the comparison result, return false
    changedAttributes : function(diff) {

      // If diff is not specified, the data set that has changed from the previous state of the current model. These data have been stored in the changed attribute, so a copy of the changed collection will be returned.
      if(!diff)

        return () ? _.clone() : false;

      // Specifies the diff set that needs to be compared, and returns the comparison result of the last data and the diff set.
      // The old variable stores the model data of the previous state
      var val, changed = false, old = this._previousAttributes;

      // Iterate through the diff collection and compare each item with the set of the previous state
      for(var attr in diff) {

        // Temporarily store data with inconsistent comparison results to changed variable
        if(_.isEqual(old[attr], ( val = diff[attr])))

          continue;

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

      }

      // Return the comparison result
      return changed;

    },

    // In the change event triggered by the model, obtain the data of a certain attribute that has been changed to the previous state, which is generally used to compare or rollback data.
    // This method is usually called in the change event. After the change event is triggered, the _previousAttributes attribute stores the latest data
    previous : function(attr) {

      // attr specifies the attribute name that needs to be obtained in the previous state
      if(! || !this._previousAttributes)

        return null;

      return this._previousAttributes[attr];

    },

    // In the model trigger change event, get the data set of the previous state of all attributes
    // This method is similar to the previous() method, which is generally called in the change event and is used for data comparison or rollback
    previousAttributes : function() {

      //Clone the data object of the previous state into a new object 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 whether the data in the current model can be verified by the validate method. Please make sure that the validate method is defined before calling.
    isValid : function() {

      return !();

    },

    // Data verification method is automatically executed when data update methods such as set, save, add, etc. are called
    // The "error" event of the model object will be triggered. If an error handler is specified in options, only the function will be executed.
    // @param {Object} attrs attributes attributes of the data model, storing the objected data of the model
    // @param {Object} options configuration item
    // @return {Boolean} Verification returns true, not return false
    _validate : function(attrs, options) {

      // If properties are set when calling set, save, add and other data update methods, verification is ignored
      // If the validate method is not added in the Model, the verification is ignored
      if( || !)

        return true;

      // Get all attribute values ​​in the object and put them in the validate method for verification
      // The validate method contains 2 parameters, namely the data collection and configuration objects in the model. If the verification is passed, no data will be returned (default is undefined). If the verification fails, data with error message will be returned.
      attrs = _.extend({}, , attrs);

      var error = (attrs, options);

      // Verification passed
      if(!error)

        return true;

      // Verification failed
      // If an error error handling method is set in the configuration object, the method is called and the error data and configuration object are passed to the method
      if(options &amp;&amp; ) {

        (this, error, options);

      } else {

        // If the error event is bound to the model, the binding event will be triggered
        ('error', this, error, options);

      }

      // Return to the verification failed to pass the flag
      return false;

    }

  });

 

  // Data model collection related
  // -------------------

 

  // Collection collection stores a series of data models of the same class and provides relevant methods to operate on the model
  var Collection =  = function(models, options) {

    //Configuration object
    options || ( options = {});

    // Set the collection's model class in the configuration parameters
    if()

       = ;

    // If the comparator attribute is set, the data in the collection will be sorted according to the sorting algorithm in the comparator method (it will be automatically called in the add method)
    if()

       = ;

    // Reset the internal state of the collection during instantiation (it can be understood as a definition state when called for the first time)
    this._reset();

    // Call custom initialization method, if necessary, the initialize method will generally be overloaded
    (this, arguments);

    // If models data is specified, the reset method is called to add the data to the collection
    // The silent parameter is set during the first call, so the "reset" event will not be triggered
    if(models)

      (models, {

        silent : true,

        parse : 

      });

  };

  // Define the collection class prototype method through the extend method
  _.extend(, Events, {

 

    // Define the model class of the collection, the model class must be a subclass of
    // When using set-related methods (such as add, create, etc.), data objects are allowed to be passed in. The set-based method will automatically create corresponding instances based on the defined model class.
    // The data models stored in the collection should be instances of the same model class
    model : Model,

 

    // Initialization method, this method is automatically called after the collection instance is created
    // This method is generally overloaded when defining a collection class
    initialize : function() {

    },

    // Return an array containing the data objects of each model in the collection
    toJSON : function(options) {

      // Use the map method of Undersocre to form an array of toJSON results of each model in the collection and return
      return (function(model) {

        // Call the toJSON method of each model object in turn. This method will return the model's data object (copied copy) by default.
        // If you need to return a string or other forms, you can overload the toJSON method
        return (options);

      });

    },

    // Add one or more model objects to the collection
    // The "add" event will be triggered by default. If the silent attribute is set in options, the triggering of this event can be turned off.
    // The models passed in can be one or a series of model objects (instances of the Model class). If the model attribute is set in the collection, it is allowed to pass directly into the data object (such as {name: 'test'}), and the data object will be automatically instantiated as the model pointing to the model.
    add : function(models, options) {

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

      options || ( options = {});

      // models must be an array, if only one model is passed in, it will be converted to an array
      models = _.isArray(models) ? () : [models];

 

      // traverse the list of models that need to be added. During the traversal process, the following operations will be performed:
      // - Convert data objects to model objects
      // - Create a reference between the model and the collection
      // - Record invalid and duplicate models and filter them later
      for( i = 0, length = ; i &lt; length; i++) {

        // Convert the data object into a model object, references to the resume model and collection, and store it in the model (at the same time the corresponding model in the models 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");

        }

        // The cid and id of the current model
        cid = ;

        id = ;

        // Invalid or duplicate model index (index in models array) is recorded in the dups array, and filter and delete it in the next step
        // If the index of the model already exists in the cids and ids variables, it is considered that the same model has been declared multiple times in the incoming model array
        // If the index of the model already exists in the _byCid and _byId object, it is considered that the same model already exists in the current collection
        // For the above two cases, record the index of the model to dups for filtering and deletion
        if(cids[cid] || this._byCid[cid] || ((id != null) &amp;&amp; (ids[id] || this._byId[id]))) {

          (i);

          continue;

        }

        // Record the models that have been traversed in models for repeated checks in the next loop
        cids[cid] = ids[id] = model;

      }

 

      // Remove invalid or duplicate models from models, retain the list of models that really need to be added in the current collection
      i = ;

      while(i--) {

        (dups[i], 1);

      }

 

      // traverse the model that needs to be added, listen to model events and record the _byCid and _byId list, used as an index when calling get and getByCid methods
      for( i = 0, length = ; i &lt; length; i++) {

        // Listen to all events in the model and execute the _onModelEvent method
        // The add, remove, destroy and change events thrown by the model will be processed in the _onModelEvent method so that the model will remain synchronized with the state in the collection.
        ( model = models[i]).on('all', this._onModelEvent, this);

        // Record the model to the _byCid object based on the cid, so as to facilitate searching based on the cid
        this._byCid[] = model;

        // Record the model to the _byId object according to the id, so as to facilitate searching based on the id
        if( != null)

          this._byId[] = model;

      }

 

      // Change the length attribute of the set. The length attribute records the number of models in the current set.
       += length;

      // Set the position where the new model list is inserted into the collection. If the at parameter is set in options, insert it at the at position of the collection.
      // It will be inserted at the end of the collection by default
      // If the comparator custom sorting method is set, the at will be sorted according to the method in the comparator, so the final order may not be in the specified position at
      index =  != null ?  : ;

      (, [index, 0].concat(models));

      // If the comparator method is set, the data will be sorted according to the algorithm in the comparator
      // Automatic sorting uses the silent attribute to prevent the triggering of reset events
      if()

        ({

          silent : true

        });

      // Trigger the "add" event on each model object in turn. If the silent attribute is set, the event will be blocked from triggering
      if()

        return this;

      // traverse the newly added model list
      for( i = 0, length = ; i &lt; length; i++) {

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

          continue;

         = i;

        // Trigger the "add" event of the model. Because the set listens to the "all" event of the model, the set will also trigger the "add" event in the _onModelEvent method
        // For details, please refer to the _onModelEvent method.
        ('add', model, this, options);

      }

      return this;

    },

    //Remove model objects from the collection (supports removal of multiple models)
    // The models passed in can be the model object that needs to be removed, or the model's cid and model's id
    // Remove the model and will not call the destroy method of the model
    // If no parameters are set, the remove event of the model will be triggered, and the remove event of the collection will be triggered (the collection listens to all events of the model through the _onModelEvent method)
    remove : function(models, options) {

      var i, l, index, model;

      // options default to empty object
      options || ( options = {});

      // models must be an array type, when only one model is removed, put it into an array
      models = _.isArray(models) ? () : [models];

      // traverse the list of models that need to be removed
      for( i = 0, l = ; i &lt; l; i++) {

        // The model list passed in can be the model object that needs to be removed, or the model's cid and model's id
        // (In getByCid and get methods, the model can be obtained through cid and id. If the model object is passed in, it will return to the model itself)
        model = (models[i]) || (models[i]);

        // No model was obtained
        if(!model)

          continue;

        // Remove the model's id reference from the _byId list
        delete this._byId[];

        // Remove cid reference from the model from the _byCid list
        delete this._byCid[];

        // indexOf is a method in the Underscore object. Here, the indexOf method is used to obtain the first appearance of the model in the collection.
        index = (model);

        // Remove the model from the collection list
        (index, 1);

        // Reset the length attribute of the current collection (record the number of models in the collection)
        --;

        // If the silent attribute is not set, the remove event of the model is triggered
        if(!) {

          // Add the current model's position in the collection to the options object and pass it to remove to listen to the event so that it can be used in the event function
           = index;

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

        }

        // Defus the relationship between the model and the set, including references to the model and event listening in the set
        this._removeReference(model);

      }

      return this;

    },

    // Add a model object to the end of the collection
    // If the comparator sorting method is defined in the collection class, the models added by the push method will be sorted according to the algorithm defined by the comparator, so the order of the model may be changed
    push : function(model, options) {

      // Instantiate the model into a model object through the _prepareModel method. This code is redundant, because the model will be obtained through _prepareModel in the add method called below.
      model = this._prepareModel(model, options);

      // Call the add method to add the model to the collection (add to the end of the collection by default)
      (model, options);

      return model;

    },

    // Remove the last model object in the collection
    pop : function(options) {

      // Get the last model in the collection
      var model = ( - 1);

      // Remove the model by removing it
      (model, options);

      return model;

    },

    // Insert the model to the first position of the collection
    // If the comparator sorting method is defined in the collection class, the models added through the unshift method will be sorted according to the algorithm defined by the comparator, so the order of the model may be changed
    unshift : function(model, options) {

      // Instantiate the model into a model object through the _prepareModel method
      model = this._prepareModel(model, options);

      // Call the add method to insert the model into the first position of the collection (set at to 0)
      // If the comparator sort method is defined, the order of the collection will be rearranged
      (model, _.extend({

        at : 0

      }, options));

      return model;

    },

    // Remove and return the first model object in the collection
    shift : function(options) {

      // Get the first model in the set
      var model = (0);

      // Remove the model from the collection
      (model, options);

      // Return to the model object
      return model;

    },

    // Find the model from the collection based on id and return
    get : function(id) {

      if(id == null)

        return

        void 0;

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

    },

    // Find the model from the collection based on cid and return
    getByCid : function(cid) {

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

    },

    // Find the model from the collection based on the index (subscript, starting from 0) and return
    at : function(index) {

      return [index];

    },

    // Filter the models in the set by value
    // attrs is a filter object, such as {name: 'Jack'}, which will return all models (arrays) in the collection with name "Jack" (array)
    where : function(attrs) {

      // attrs cannot be null
      if(_.isEmpty(attrs))

        return [];

      // Filter the model in the set by filter method
      // The filter method is a method in Underscore. It is used to traverse elements in the collection and return elements that can be verified by the processor (return value is true) as an array.
      return (function(model) {

        // traverse the verification rules in the attrs object
        for(var key in attrs) {

          // Match the validation rules in attrs with the model in the set
          if(attrs[key] !== (key))

            return false;

        }

        return true;

      });

    },

    // Sort the models in the collection according to the method specified by the comparator attribute
    // If the silent parameter is not set in options, the reset event will be triggered after sorting
    sort : function(options) {

      // options is an object by default
      options || ( options = {});

      // The comparator attribute must be specified to call the sort method (sorting algorithm method), otherwise an error will be thrown
      if(!)

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

      // boundComparator stores comparator sorting algorithm method that binds the current collection context object
      var boundComparator = _.bind(, this);

      if( == 1) {

         = (boundComparator);

      } else {

        // Call to customize the data through the comparator algorithm
        (boundComparator);

      }

      // If no silent parameter is specified, the reset event will be triggered
      if(!)

        ('reset', this, options);

      return this;

    },

    // Store the attr attribute values ​​of all models in the collection into an array and return
    pluck : function(attr) {

      // map is a method in Underscore, used to iterate through a collection and return the return values ​​of all processors as an array
      return _.map(, function(model) {

        // Return the attr attribute value of the current model
        return (attr);

      });

    },

    // Replace all model data in the set (models)
    // This operation will delete all current data and status in the collection and reset the data to models
    // models should be an array that can contain a series of Model Model objects, or original objects (which will be automatically created as model objects in the add method)
    reset : function(models, options) {

      // models are the model (or data) arrays that are replaced
      models || ( models = []);

      // options is an empty object by default
      options || ( options = {});

      // Iterate through the models in the current collection, delete and disable their reference relationship with the collection in turn
      for(var i = 0, l = ; i &lt; l; i++) {

        this._removeReference([i]);

      }

      // Delete the collection data and reset the status
      this._reset();

      // Add new model data to the collection through the add method
      // Here the configuration item is overwritten to a new object through the extted method. The default silent is true, so the "add" event will not be triggered
      // If the silent attribute is not set when calling the reset method, the reset event will be triggered. If set to true, no events will be triggered. If set to false, the "add" and "reset" events will be triggered in turn.
      (models, _.extend({

        silent : true

      }, options));

      // If the silent attribute is not set when calling the reset method, the reset event will be triggered
      if(!)

        ('reset', this, options);

      return this;

    },

    // Get the initialization data of the collection from the server
    // If the parameter add=true is set in options, the obtained data will be appended to the collection. Otherwise, the current data in the collection will be replaced with the data returned by the server.
    fetch : function(options) {

      // Copy options object, because the options object will be modified later to temporarily store data
      options = options ? _.clone(options) : {};

      if( === undefined)

         = true;

      // collection records the current collection object, used in success callback function
      var collection = this;

      // Custom callback function. After the data request is successful and the addition is completed, the custom success function will be called
      var success = ;

      // When the data requested from the server is successful, the function will parse and add data
       = function(resp, status, xhr) {

        // Use the parse method to parse the data returned by the server. If you need to customize the data structure, you can overload the parse method
        // If add=true is set in options, the add method is called to add data to the collection. Otherwise, the data in the collection will be replaced with the server's return data through the reset method.
        collection[ ? 'add' : 'reset']((resp, xhr), options);

        // If a custom successful callback is set, execute
        if(success)

          success(collection, resp);

      };

      // When the server returns a status error, use wrapError method to handle the error event
       = (, collection, options);

      // Call method to send a request to get data from the server
      // If the required data is not obtained from the server, or the method does not use AJAX, you can overload the method
      return ( || ).call(this, 'read', this, options);

    },

    // Add and create a model to the collection and save the model to the server
    // If you are creating a model through a data object, you need to declare the model class corresponding to the model attribute in the collection
    // If the wait attribute is declared in options, the model will be added to the collection after the server is created successfully. Otherwise, the model will be added to the collection first and then saved to the server (regardless of whether the saving is successful or not)
    create : function(model, options) {

      var coll = this;

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

      // Get an instance of the model class through _prepareModel
      model = this._prepareModel(model, options);

      // Model creation failed
      if(!model)

        return false;

      // If the wait attribute is not declared, add the model to the collection through the add method
      if(!)

        (model, options);

      // Custom callback function after success store saved to the server (by declaration)
      var success = ;

      // Callback function after listening to model data is saved successfully
       = function(nextModel, resp, xhr) {

        // If the wait attribute is declared, the model will be added to the collection only after the server saves successfully
        if()

          (nextModel, options);

        // If a custom successful callback is declared, a custom function will be executed, otherwise the model's sync event will be triggered by default
        if(success) {

          success(nextModel, resp);

        } else {

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

        }

      };

      // Call the save method of the model to save the model data to the server
      (null, options);

      return model;

    },

    // Data analysis method, used to parse server data into structured data available to models and collections
    // The default will return resp itself, which requires the server to define the data format supported by Backbone. If you need to customize the data format, you can overload the parse method
    parse : function(resp, xhr) {

      return resp;

    },

    // chain is used to build a chain operation of collection data. It converts the data in the collection into an Underscore object and uses the chain method of Underscore to convert it into a chain structure.
    // For the conversion method of chain method, please refer to the comments of chain method in Underscore
    chain : function() {

      return _().chain();

    },

    // Delete all collection elements and reset the data state in the collection
    _reset : function(options) {

      // Delete the collection element
       = 0;

       = [];

      // Reset the collection status
      this._byId = {};

      this._byCid = {};

    },

    // Some preparations before adding the model to the collection
    // Including instantiating data into a model object and referring collection to the model's collection attribute
    _prepareModel : function(model, options) {

      options || ( options = {});

      // Check whether the model is a model object (i.e. an instance of the Model class)
      if(!( model instanceof Model)) {

        // The model passed in is a model data object, not a model object
        // Pass data as parameters to Model to create a new model object
        var attrs = model;

        // Set the collection of model references
         = this;

        // Convert data into models
        model = new (attrs, options);

        // Verify the data in the model
        if(!model._validate(, options))

          model = false;

      } else if(!) {

        // If the incoming is a model object but no reference to the collection is established, set the collection property of the model to the current collection
         = this;

      }

      return model;

    },

    // Unbind the relationship between a model and a collection, including references to the collection and event listening
    // Generally, it is automatically called when calling remove method to delete the model or calling reset method to reset the state
    _removeReference : function(model) {

      // If the model references the current collection, remove the reference (must make sure that all references to the model have been uninstalled, otherwise the model may not be freed from memory)
      if(this == ) {

        delete ;

      }

      // Cancel all model events listened in the collection
      ('all', this._onModelEvent, this);

    },

    // is automatically called when adding a model to a collection
    // Used to listen to the events of the model in the collection. When the model triggers the event (add, remove, destroy, change event) the collection performs related processing
    _onModelEvent : function(event, model, collection, options) {

      // Add and remove the event of the model, you must ensure that the collection to which the model belongs is the current collection object
      if((event == 'add' || event == 'remove') &amp;&amp; collection != this)

        return;

      // When the model triggers a destruction event, remove it from the collection
      if(event == 'destroy') {

        (model, options);

      }

      // When the model's id is modified, the collection modification _byId stores references to the model and maintains synchronization with the model id, making it easier to use the get() method to obtain the model object
      if(model &amp;&amp; event === 'change:' + ) {

        // Get the id of the model before changing, and remove it from the _byId list of the collection based on this id
        delete this._byId[()];

        // Use the new id of the model as the key, and store references to the model in the _byId list
        this._byId[] = model;

      }

      // Trigger the corresponding event of the model in the set. No matter what event the model triggers, the set will trigger the corresponding event.
      // (For example, when the model is added to the collection, the "add" event of the model will be triggered, and the "add" event of the collection will also be triggered in this method)
      // This is very effective for listening and handling changes in model state in sets
      // In the listening set event, the model that triggers the corresponding event will be passed as a parameter to the listening function of the set
      (this, arguments);

    }

  });

 

  // Define the relevant methods of collection operations in Underscore
  // Copy a series of collection operation methods in Underscore into the prototype object of the Collection collection class
  // This way, you can directly call the Underscore-related collection method through the collection object
  // The collection data operated by these methods when invoked is the models data of the current Collection object
  var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];

 

  // traverse the defined method list
  _.each(methods, function(method) {

    // Copy the method to the prototype object of the Collection collection class
    [method] = function() {

      // Use the Underscore method directly when calling, and the context object remains as the Underscore object
      // It should be noted that the collection parameters passed to the Underscore method here are , so when using these methods, the operational collection object is the model data of the current Collection object
      return _[method].apply(_, [].concat(_.toArray(arguments)));

    };

  });

  // URL router
  // -------------------

 

  // Implement custom routers through inheritance classes
  // The router allows you to define routing rules, navigate through URL fragments, and correspond to each rule to a method. This method will be automatically executed when the URL matches a certain rule.
  // The router navigates through URLs. The navigation methods are divided into pushState, Hash, and listening methods (refer to the class for details)
  // When creating a Router instance, set the listening method corresponding to a routing rule by setting the monitoring method
  // The routing rules in // are organized according to {rule name: method name}. The method corresponding to each routing rule must be a declared method in the Router instance.
  // The defined routing rules are matched in sequence. If the current URL can be matched by multiple rules, only the first matching event method will be executed
  var Router =  = function(options) {

    // options is an empty object by default
    options || ( options = {});

    // If the routes object (routing rules) is set in options, the routes attribute is assigned to the current instance
    // The routes attribute records the binding relationship between routing rules and event methods. When the URL matches a certain rule, the associated event method will be automatically called.
    if()

       = ;

    // parse and bind routing rules
    this._bindRoutes();

    // Call custom initialization method
    (this, arguments);

  };

  // Define the search conditions when converting the routing rules in the form of a string into executable regular expression rules
  // (Routing rules in string form, matched by \w+, so only strings composed of alphanumeric and underscores are supported)
  // Match dynamic routing rules in a URL fragment (separated by /"slash")
  // For example: (topic/:id) matches (topic/1228), listens to event function(id) { // id is 1228 }
  var namedParam = /:\w+/g;

  // Match dynamic routing rules in the entire URL fragment
  // For example: (topic*id) match (url#/topic1228), listen to event function(id) { // id is 1228 }  var splatParam = /\*\w+/g;

  // Match special characters in URL fragments and add escape characters before the characters to prevent special characters from becoming metacharacters after being converted into regular expressions
  // For example: (abc)^[,.] will be converted to \(abc\)\^\[\,\.\]
  var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;

 

  // Extend properties and methods to the prototype object of the Router class
  _.extend(, Events, {

 

    // Custom initialization method, automatically called after router Router instantiation
    initialize : function() {

    },

    // Bind a routing rule to a listening event. When the URL fragment matches the rule, the event will be automatically called to trigger it.
    route : function(route, name, callback) {

      // Create history instance, which is a singleton object, which is only instantiated when the router object is created for the first time
       || ( = new History);

      // Check whether the route rule name is a string (when the route method is manually called to create a routing rule, it is allowed to pass a regular expression or string as a rule)
      // The rules passed in when constructing the Router instance should be a string (because the key in the routes configuration is used as the routing rule in the _bindRoutes method)
      // If the route rule of string type is passed in, convert it into a regular expression through the _routeToRegExp method to match URL fragments
      if(!_.isRegExp(route))

        route = this._routeToRegExp(route);

      // If callback (event method) is not set, the method with the same name as the name is obtained from the current Router instance based on the name
      // This is because the callback method may not be passed when the route method is called manually, but the name event name must be passed, and the method has been defined in the Router instance
      if(!callback)

        callback = this[name];

      // Call the route method of the history instance, which binds the converted regular expression rules and listen event methods to the list so that history can route and control it
      // When the history instance matches the corresponding routing rule and calls the event, the URL fragment will be passed to the event method as a string (i.e. fragment parameter).
      // Here, there is no direct way to the route method of history, but another function is encapsulated using the bind method, and the execution context of the function is the current Router object
      (route, _.bind(function(fragment) {

        // Call the _extractParameters method to get the parameters in the matched rule
        var args = this._extractParameters(route, fragment);

        // Call callback route to listen to events and pass parameters to listen to events
        callback &amp;&amp; (this, args);

        // Trigger route:name event, name is the event name passed when calling route
        // If the route:name event is bound to the current Router instance using the on method, a trigger notification will be received
        (this, ['route:' + name].concat(args));

        // Trigger the bound route event in the history instance. When the router matches any rules, the event will be triggered
        ('route', this, name, args);

        /**

          * Event bindings are as follows:

          * var router = new MyRouter();

          * ('route:routename', function(param) {

          * // Event bound to a rule in the Router instance will be triggered when the rule is matched

          * });

          * ('route', function(router, name, args) {

          * // Event bound to history instance, triggered when matching any rules

          * });

          * ();

          */

      }, this));

      return this;

    },

    // Manually set jump to URL by calling the method
    navigate : function(fragment, options) {

      // Navigate method for proxying to history instance
      (fragment, options);

    },

    // parse the route() rules defined by the current instance, and call the route method to bind each rule to the corresponding method
    _bindRoutes : function() {

      // If the routes rules are not set when creating an object, no parsing and binding are performed
      if(!)

        return;

      // Routes variable stores routing rules in reverse order in the form of a two-dimensional array
      // For example [['', 'homepage'], ['controller:name', 'toController']]
      var routes = [];

      // traverse routes configuration
      for(var route in ) {

        // Put the routing rules into a new array and organize them according to [rule name, binding method]
        // Place the array on the top of routes through the unshift method to achieve reverse order arrangement
        // Here we arrange the rules in routes in reverse order. When the route method is called later, unshift will be called again to reverse the order to ensure that the final order is executed in the order defined in routes configuration.
        // After switching the order twice, the order before the initial call will be restored. This is because the user can manually call the route method to dynamically add routing rules, and the manually added routing rules will be added to the first in the list. Therefore, unshift should be used in the route method to insert the rules
        // The rules automatically added when constructing Router instances are to maintain the definition order, so the defined rules are arranged in reverse order here.
        ([route, [route]]);

      }

      // The loop is completed, and the routes are stored in reverse order routing rules.
 

      // Loop the routing rules, and call the route method in turn to bind the rule name to the specific event function
      for(var i = 0, l = ; i &lt; l; i++) {

        // Call the route method and pass it separately (rule name, event function name, event function object)
        (routes[i][0], routes[i][1], this[routes[i][1]]);

      }

    },

    // Convert routing rules in string form to regular expression objects
    // (After checking the routing rules of string type in the route method, the method will be automatically called for conversion)
    _routeToRegExp : function(route) {

      // Add escape characters to special characters in strings to prevent special characters from becoming metacharacters after being converted to regular expressions (these special characters include -[\]{}()+?.,\\^$|#\s)
      // Convert dynamic routing rules separated by /"slash" in the string to ([^\/]+), representing multiple characters starting with /"slash" in the regular
      // Convert the dynamic routing rule of the *"asterisk" in the string to (.*?), representing 0 or more arbitrary characters in the regular (non-greedy pattern is used here, so you can use a combination routing rule such as: *list/:id, which will match orderlist/123, and will pass "order" and "123" as parameters to the event method)
      // Please note that the regular expressions replaced by namedParam and splatParam are both included in the matching content using () brackets. This is to facilitate the removal of the matching content and pass it to the event method as a parameter.
      // Please note that the strings matching namedParam and splatParam are meaningless in str, str strings in str, *str are ignored after being replaced below, but the parameters of the general writing and listening event methods are of the same name for identification
      route = (escapeRegExp, '\\$&amp;').replace(namedParam, '([^\/]+)').replace(splatParam, '(.*?)');

      // Create the converted string as a regular expression object and return
      // This regular expression will be used to match URL fragments according to the rules in the route string
      return new RegExp('^' + route + '$');

    },

    // Pass in a routing rule (regular expression) and URL fragment (string) to match, and return to get parameters from the matching string
    /**

      * For example, the routing rule is 'teams/:type/:id', and the corresponding regular expression will be converted to /^teams/([^/]+)/([^/]+)$/, (For the process of converting routing rules to regular expressions, please refer to the _routeToRegExp method)

      * The URL fragment is 'teams/35/1228'

      * Then the result after exec execution is ["teams/35/1228", "35", "1228"]

      * An element in the array is the URL fragment string itself, starting from the second one, it is the parameters in the routing rule expression in sequence

      */

    _extractParameters : function(route, fragment) {

      return (fragment).slice(1);

    }

  });

 

  // Router management
  // ----------------

 

  // History class provides routing management-related operations, including listening for URL changes, (listening through popstate and onhashchange events, and monitoring through setInterval heartbeat for browsers that do not support events)
  // Provide verification of matching routing rules with the current URL, and triggering related listening events
  // History will not be called directly. When the Router object is instantiated for the first time, a singleton of History will be automatically created (by access)
  var History =  = function() {

    // The handlers attribute records the rules and listener lists that have been set in all currently routed objects
    // Formats such as: [{route: route, callback: callback}], route records regular expression rules, callback records listening events when matching rules
    // When the history object hears that the URL changes, it will automatically match the rules defined in handlers and call the listening event
     = [];

    // Bind the context object of the checkUrl method to the history object, because the checkUrl method is used as a callback function for popstate and onhashchange events or setInterval, the context object will be changed when the callback is executed
    // checkUrl method is used to check and call loadUrl method when listening to a change in the URL.
    _.bindAll(this, 'checkUrl');

  };

  // Define whether the first character in the URL fragment is "#" or "/"  var routeStripper = /^[#\/]/;

 

  // Define whether the string obtained from the userAgent contains the IE browser's identity, which is used to determine whether the current browser is IE
  var isExplorer = /msie [\w.]+/;

 

  // Record whether the current history singleton object has been initialized (call the start method)
   = false;

 

  // Add methods to the prototype object of the History class. These methods can be called through the History instance (i.e. the object)
  _.extend(, Events, {

 

    // When the user uses a lower version of IE browser (not supporting onhashchange event), listen for changes in routing status through heartbeat
    // The interval property sets the heartbeat frequency (milliseconds). If this frequency is too low, it may cause delay. If it is too high, it may consume CPU resources (the device configuration needs to be considered when the user uses a low-end browser)
    interval : 50,

 

    // Get the Hash string in location (Anchor point The clip after #)    getHash : function(windowOverride) {

      // If a window object is passed in, it will be retrieved from the object, otherwise it will be retrieved from the current window object by default
      var loc = windowOverride ?  : ;

      // Extract the string after # and return it      var match = (/#(.*)$/);

      // If no matching content is found, an empty string is returned
      return match ? match[1] : '';

    },

    // Process and return the routing fragment in the current URL according to the currently set routing method
    getFragment : function(fragment, forcePushState) {

      // fragment is a pending routing fragment that has been extracted from the URL (such as #/id/1288)
      if(fragment == null) {// If no fragment is passed, the fetch is performed according to the current routing method
        if(this._hasPushState || forcePushState) {

          // Use pushState to route
          // fragment records the URL path after the current domain name
          fragment = ;

          // Search records the parameters after the current page
          var search = ;

          // Merge paths and parameters together as pending routing fragments
          if(search)

            fragment += search;

        } else {

          // Use hash method to route
          // Get the current anchor point through the getHash method (The string after #) is used as a routing fragment          fragment = ();

        }

      }

      // According to the root parameters set in the configuration item, the content after the root path is taken out from the routing fragment
      if(!())

        fragment = ();

      // If the first letter of the URL fragment is "#" or "/", remove the character      // Return the URL fragment after processing
      return (routeStripper, '');

    },

    // Initialize the History instance, this method will only be called once, and should be automatically called after creating and initializing the Router object.
    // This method serves as the scheduler for the entire route. It will monitor changes in URL fragments for different browsers, and is responsible for verifying and notifying the listener function.
    start : function(options) {

      // If the history object has been initialized, an error will be thrown
      if()

        throw new Error(" has already been started");

      // Set the initialization state of history object
       = true;

 

      // Set configuration items, use the options configuration item passed when calling the start method to override the default configuration
       = _.extend({}, {

        // Root property sets the route root directory in URL navigation
        // If you use pushState for routing, the address after the root directory will generate different addresses according to different routes (this may be located to different pages, so you need to ensure that the server supports it)
        // If you use the Hash anchor method for routing, root means the anchor point (#) after the URL        root : '/'

      }, , options);

      /**

        * History implements 3 ways of listening for different browser features:

        * - For browsers that support popstate events in HTML5, listen through popstate events

        * - For browsers that do not support popstate, use the onhashchange event for listening (the URL set by changing hash (anchor point) will trigger the onhashchange event when it is loaded)

        * - For browsers that do not support popstate and onhashchange events, listen by keeping heartbeat

        *

        * About the related methods of popstate event in HTML5:

        * - pushState can add a new history entity to the browser history to the specified URL

        * - replaceState method can replace the current history entity with the specified URL

        * When using pushState and replaceState methods, only the current URL is replaced, and it will not really go to this URL (when using the back or forward buttons, it will not jump to this URL)

        * (These two methods can solve the problem of browser advancement and backward operations in AJAX single page application)

        * When the URL replaced by pushState or replaceState methods is replaced, the onpopstate event will be triggered when loaded

        * Browser support:

        * Chrome 5, Firefox 4.0, IE 10, Opera 11.5, Safari 5.0

        *

        * Notice:

        * - The method uses Hash method to navigate by default

        * - If you need to enable pushState for navigation, you need to manually pass the configuration when calling the start method

        * (Please make sure that the browser supports pushState feature before setting, otherwise it will be converted to Hash by default)

        * - When using pushState for navigation, the URL may change from the specified root directory, which may navigate to different pages, so make sure that the server already supports pushState for navigation.

        */

      // _wantsHashChange attribute records whether you want to use hash (anchor point) to record and navigate the router
      // Unless you manually set hashChange to false in the options configuration item, the hash anchor will be used by default
      // (If it is manually set to true and the browser supports the pushState feature, the pushState method will be used)
      this._wantsHashChange =  !== false;

      // _wantsPushState property records whether you want to use pushState to record and navigate the router
      // pushState is a new feature added in HTML5. If it is not manually declared as true, the hash method will be used by default.
      this._wantsPushState = !!;

      // _hasPushState property records whether the browser supports pushState feature
      // If pushState is set in options (that is, I want to use pushState method), check whether the browser supports this feature
      this._hasPushState = !!( &amp;&amp;  &amp;&amp; );

      // Get the routing string in the current URL
      var fragment = ();

      // documentMode is a unique property of IE browser, used to identify the rendering mode used by the current browser
      var docMode = ;

      // oldIE is used to check whether the current browser is a lower version of IE browser (i.e. IE 7.0 or below)
      // This code can be understood as: the current browser is IE, but the documentMode property does not support it, or the rendering mode returned by the documentMode property is IE7.0 or below
      var oldIE = ((()) &amp;&amp; (!docMode || docMode &lt;= 7));

 

      if(oldIE) {

        // If the user uses a lower version of IE browser, popstate and onhashchange events are not supported
        // Insert a hidden iframe into the DOM and implement routing by changing and heartbeat the URL of the iframe to monitor the iframe
         = $('&lt;iframe src="javascript:0" tabindex="-1" /&gt;').hide().appendTo('body')[0].contentWindow;

        // Set the iframe to the current URL fragment through navigate, which will not really load on a page, because fragment is not a complete URL
        (fragment);

      }

 

      // Start listening for routing state changes
      if(this._hasPushState) {

        // If the pushState method is used and the browser supports this feature, the popstate event is listened to the checkUrl method
        $(window).bind('popstate', );

      } else if(this._wantsHashChange &amp;&amp; ('onhashchange' in window) &amp;&amp; !oldIE) {

        // If you use Hash method for routing and the browser supports onhashchange event, listen to the hashchange event to the checkUrl method
        $(window).bind('hashchange', );

      } else if(this._wantsHashChange) {

        // For lower version browsers, the checkUrl method is monitored by the setInterval method, and the interval property identifies the heartbeat frequency
        this._checkUrlInterval = setInterval(, );

      }

 

      // Record the current URL fragment
       = fragment;

      // Verify that it is currently in the root path (that is, the path configured in it)
      var loc = ;

      var atRoot =  == ;

 

      // If the user accesses the current address through the pushState URL, the browser used by the user at this time does not support the pushState feature
      // (This may be that a user accesses the application through pushState and then shares the address with other users, but the browser of other users does not support this feature)
      if(this._wantsHashChange &amp;&amp; this._wantsPushState &amp;&amp; !this._hasPushState &amp;&amp; !atRoot) {

        // Get the URL fragment in the current pushState method and reopen the page through Hash method
         = (null, true);

        // For example, the URL of hashState is /root/topic/12001, and the URL of the reopened Hash is /root#topic/12001
        ( + '#' + );

        return true;

 

        // If the user accesses the current address through the Hash URL, but sets pushState when calling the method (hopefully route it through pushState)
        // And the user's browser supports the pushState feature, then replace the current URL with the pushState method (note that the replaceState method is used here to replace the URL, and the page will not be refreshed)
        // The following branch conditions can be understood as: If we want to use pushState for routing, and the browser supports this feature, the user also uses the Hash method to open the current page
        // (This may be that a user uses Hash to browse to a URL and share the URL to another browser that supports pushState feature. This branch will be executed when the user accesses)
      } else if(this._wantsPushState &amp;&amp; this._hasPushState &amp;&amp; atRoot &amp;&amp; ) {

        // Get the Hash fragment in the URL and clear the first "#"or"/" string         = ().replace(routeStripper, '');

        // Use the replaceState method to replace the URL of the current browser with the supported method of pushState, that is: protocol //host address /URL path /Hash parameters, for example:
        // When the URL of the user accesses the Hash method is /root/#topic/12001, will be replaced with /root/topic/12001        // Note:
        // There are 3 parameters of pushState and replaceState methods, namely state, title, url
        // -state: Used to store inserted or modified history entity information
        // -title: Used to set the browser title (which is a reserved parameter, the browser has not implemented this feature yet)
        // -url: Set the URL address of the history entity (can be absolute or relative path, but cannot set a cross-domain URL)
        ({}, ,  + '//' +  +  + );

      }

 

      // Generally, loadUrl will be automatically called when calling the start method, matching the routing rules corresponding to the current URL fragment, and calling the method of the rule
      // If the silent attribute is set to true, the loadUrl method will not be called
      // This situation usually occurs when the stop method is called to reset the history object state, and the start method is called again to start (in fact, it is not initialized at this time, so the silent attribute will be set)
      if(!) {

        return ();

      }

    },

    // Stop history's monitoring of routes and restore the status to an unlisted state
    // After calling the stop method, you can call the start method again to start listening. Generally, after users call the start method, they need to reset the parameters of the start method, or use it for unit testing.
    stop : function() {

      // Unlisten the onpopstate and onhashchange events of the browser route
      $(window).unbind('popstate', ).unbind('hashchange', );

      // Stop heartbeat monitoring for lower versions of IE browser
      clearInterval(this._checkUrlInterval);

      // Restore the started state, which facilitates the restart of the start method next time
       = false;

    },

    // Bind a routing rule (parameter route, type regular expression) and event (parameter callback) to handlers (this method is automatically called by the Router instance)
    route : function(route, callback) {

      // Insert route and callback into the first position of the handlers list
      // This is to ensure that the rules passed in when route is called last are matched first
      ({

        // Routing rules (regular)
        route : route,

        // Methods to execute when matching rules
        callback : callback

      });

    },

    // Check whether the current URL has changed relative to the previous state
    // If there is a change, record the new URL status and call the loadUrl method to trigger the new URL and the method that matches the routing rules
    // This method is automatically called after the onpopstate and onhashchange events are triggered, or is called regularly by setInterval heartbeat in the lower version of IE browser
    checkUrl : function(e) {

      // Get the current URL fragment
      var current = ();

      // For the lower version of IE browser, the latest URL fragment will be obtained from the iframe and assigned to the current variable
      if(current ==  &amp;&amp; )

        current = (());

      // If the current URL does not change from the previous state, the execution will be stopped
      if(current == )

        return false;

      // After the execution is here, the URL has changed. Call the navigate method to set the URL to the current URL
      // When the navigate method is automatically called, the options parameter is not passed, so the loadUrl method in the navigate method will not be triggered
      if()

        (current);

      // Call the loadUrl method, check the matching rules, and execute the rule-bound methods
      // If the method is called without success, try to pass the re-acquisitioned current hash to the method when the loadUrl method is called
      () || (());

    },

    // Match the rules in the handler route list according to the current URL
    // If the URL complies with a certain rule, then execute the method corresponding to this rule, and the function will return true
    // If no appropriate rule is found, false will be returned
    // The loadUrl method will usually be called automatically when calling the start method during page initialization (unless the silent parameter is set to true)
    // - Or when the user changes the URL, the checkUrl listens to the URL change and is called
    // - Or is called when the navigate method is called manually navigated to a URL
    loadUrl : function(fragmentOverride) {

      // Get the current URL fragment
      var fragment =  = (fragmentOverride);

      // Call Undersocre any method to match the URL fragment with all rules in handlers in sequence
      var matched = _.any(, function(handler) {

        // If the rules in handlers match the current URL fragment, execute the method corresponding to the sum, and return true
        if((fragment)) {

          (fragment);

          return true;

        }

      });

      // matched is the return value of any method. If the rule is matched, it will return true, and no match will return false
      return matched;

    },

    // Navigate to the specified URL
    // If trigger is set in options, the URL that triggers the navigation and the corresponding routing rules event
    // If replace is set in options, the current URL in history will be replaced with the URL that needs to be navigated.
    navigate : function(fragment, options) {

      // If the start method is not called, or the stop method has been called, the navigation cannot be done
      if(!)

        return false;

      // If the options parameter is not an object, but a true value, the default trigger configuration item is true (that is, the event that triggers the navigation URL and the corresponding routing rules)
      if(!options || options === true)

        options = {

          trigger : options

        };

      // Remove the "#"or"/" of the first character from the passed fragment (URL fragment)      var frag = (fragment || '').replace(routeStripper, '');

      // If the current URL does not change from the URL that needs to be navigated, the execution will not continue
      if( == frag)

        return;

 

      // If you currently support and use pushState for navigation
      if(this._hasPushState) {

        // Construct a complete URL. If the current URL fragment does not contain the root path, use the root path to connect the URL fragment
        if(() != 0)

          frag =  + frag;

        // Set a new URL
         = frag;

        // If the replace attribute is set in the options option, replace the new URL to the current URL in history. Otherwise, the new URL will be appended to the history by default.
        [ ? 'replaceState' : 'pushState']({}, , frag);

 

        // If you use hash method to navigate
      } else if(this._wantsHashChange) {

        // Set a new hash
         = frag;

        // Call the _updateHash method to update the current URL to the new hash, and pass the replace configuration in options to the _updateHash method (replace or append a new hash in this method)
        this._updateHash(, frag, );

        // For lower versions of IE browser, when Hash changes, update the Hash in the iframe URL
        if( &amp;&amp; (frag != (()))) {

          // If the replace parameter is used to replace the current URL, replace the iframe with the new document directly
          // Call to open a new document to erase the contents in the current document (the close method is called here to close the document's status)
          // There is no use of write or writeln methods to output content between open and close methods, so this is an empty document
          if(!)

            ().close();

          // Call the _updateHash method to update the URL in the iframe
          this._updateHash(, frag, );

        }

 

      } else {

        // If you manually set the hashChange parameter to true when calling the start method, you do not want to use pushState and hash methods to navigate
        // Then directly jump to the new URL
        ( + fragment);

      }

      // If the trigger attribute is set in the options configuration item, the loadUrl method is called to find the routing rule and execute the corresponding events of the rule
      // When the URL changes, the state heard through the checkUrl method will be automatically called in the checkUrl method
      // When calling the navigate method manually, if you need to trigger the routing event, you need to pass the trigger parameter
      if()

        (fragment);

    },

    // Update or set the Has string in the current URL. The _updateHash method is automatically called when navigating using hash method (in the navigate method)
    // location is an object that needs to be updated
    // fragment is a hash string that needs to be updated
    // If you need to replace the new hash to the current URL, you can set replace to true
    _updateHash : function(location, fragment, replace) {

      // If replace is set to true, use the method to replace the current URL
      // After replacing the URL with the replace method, the new URL will occupy the position of the original URL in history history
      if(replace) {

        // Combine the current URL with hash into a complete URL and replace it
        (().replace(/(javascript:|#).*$/, '') + '#' + fragment);

      } else {

        // No replacement method is used, it is directly set to the new hash string
         = fragment;

      }

    }

  });

 

  // View related
  // -------------

 

  // The view class is used to create interface control objects that are lowly coupled to data. By binding the rendering method of the view to the change event of the data model, the view will be notified to render when the data changes.
  // The el in the view object is used to store the most parent layer element of the DOM required to operate in the current view. This is mainly to improve the search and operation efficiency of elements. Its advantages include:
  // - When searching or operating elements, limit the scope of the operation to the el element, and no need to search in the entire document tree
  // - When binding events for elements, you can easily bind events to el elements (the default will also be bound to el elements) or its child elements
  // - In design pattern, limit the elements, events, and logic of a view related to the view to the scope of the view, reducing the coupling between the view (at least logically)
  var View =  = function(options) {

    // Create a unique identifier for each view object, prefixed with "view"
     = _.uniqueId('view');

    // Set initialization configuration
    this._configure(options || {});

    // Set or create elements in the view
    this._ensureElement();

    // Call custom initialization method
    (this, arguments);

    // parse the event list set in options and bind the event to the elements in the view
    ();

  };

  // Define the regular to parse event names and elements in events parameters
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;

 

  // The viewOptions list records some column attribute names. When constructing a view object, if the passed configuration item contains these names, the attributes are copied to the object itself
  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];

 

  // Add some methods to the prototype object of the view class
  _.extend(, Events, {

 

    // If the specified el element is not set when creating the view object, an element will be created through the make method, and tagName is the default tag for the created element
    // You can also override the default "div" tag by customizing tagName in options
    tagName : 'div',

 

    // Each view has a $ selector method, which is similar to jQuery or Zepto, and gets elements by passing an expression
    // But this method will only search within the $el element range of the view object, so it will improve matching efficiency
    $ : function(selector) {

      return this.$(selector);

    },

    // Initialization method, automatically called after the object is instantiated
    initialize : function() {

    },

    // The render method is similar to the initialize method, and no logic is implemented by default
    // This method is generally overloaded to achieve rendering of elements in the view
    render : function() {

      // Return the current view object to support chain operations of the method
      // Therefore, if the method is overloaded, it is recommended to return the view object at the end of the method (this)
      return this;

    },

    // Remove the $el element of the current view
    remove : function() {

      // By calling jQuery or Zepto's remove method, all events and data bound to this element will be removed at the same time in the third-party library.
      this.$();

      return this;

    },

    // Create and return a DOM element based on the incoming tag name, attribute and content
    // This method is used to automatically call when created internally
    make : function(tagName, attributes, content) {

      // Create elements based on tagName
      var el = (tagName);

      // Set element properties
      if(attributes)

        $(el).attr(attributes);

      // Set element content
      if(content)

        $(el).html(content);

      // Return element
      
return el;

    },

    // Set the standard $el and el attributes for the view object. This method is automatically called when the object is created.
    // $el is an object created through jQuery or Zepto, el is a standard DOM object
    setElement : function(element, delegate) {

      // If the $el attribute already exists (maybe the element that switches the view by manually calling the setElement method), the events that were previously bound to $el will be cancelled (see undelegateEvents method for details)
      if(this.$el)

        ();

      // Create the element as a jQuery or Zepto object and store it in the $el attribute
      this.$el = ( element instanceof $) ? element : $(element);

      // Store standard DOM objects
       = this.$el[0];

      // If the delegate parameter is set, the event set for the event parameter in the element binding view
      // In the constructor of the view class, the delegateEvents method has been called for binding, so the delegate parameter is not passed when the setElement method is called in the initialized _ensureElement method
      // When manually calling the setElement method to set the view element, the delegate binding event is allowed to be passed
      if(delegate !== false)

        ();

      return this;

    },

    // Bind events for view elements
    // The events parameter configures a collection of events that need to be bound, in the format such as ('Event Name Element Selection Expression': 'Event Method Name/or Event Function'):
    // {

    //   'click #title': 'edit',

    //   'click .save': 'save'

    //   'click span': function() {}

    // }

    // This method will be automatically called when the view object is initialized, and the events attribute in the object is used as events parameter (event collection)
    delegateEvents : function(events) {

      // If the events parameter is not passed manually, the events attribute is obtained from the view object as the event collection
      if(!(events || ( events = getValue(this, 'events'))))

        return;

      // Cancel the currently bound events event
      ();

      // traverse the list of events that need to be bound
      for(var key in events) {

        // Get the method that needs to be bound (allowed to be method name or function)
        var method = events[key];

        // If it is a method name, the function object is obtained from the object, so the method name must be a method defined in the view object
        if(!_.isFunction(method))

          method = this[events[key]];

        // Throw an error on the invalid method
        if(!method)

          throw new Error('Method "' + events[key] + '" does not exist');

        // parse event expression (key), parse the name of the event and the elements that need to be operated from the expression
        // For example 'click #title' will be parsed into two parts: 'click' and '#title', both stored in the match array        var match = (delegateEventSplitter);

        // eventName is the parsed event name
        // selector is the parsed event element selector expression
        var eventName = match[1], selector = match[2];

        // The bind method is a method used in Underscore to bind function context
        // Here the context of the method event method is bound to the current view object, so after the event is triggered, this in the event method always points to the view object itself
        method = _.bind(method, this);

        // Set the event name, append the identity after the event name, and the event binding method used to pass it to jQuery or Zepto
        eventName += '.delegateEvents' + ;

        //Bind events via jQuery or Zepto
        if(selector === '') {

          // If the child element selector is not set, the event and method are bound to the current $el element itself through the bind method
          this.$(eventName, method);

        } else {

          // If the child element selector expression is currently set, it is bound by delegate
          // This method will look for the child elements under the current $el element and bind the event to the element matching the selector expression
          // If the element of this selector does not belong to the child element of the current $el, the event binding is invalid
          this.$(selector, eventName, method);

        }

      }

    },

    // Cancel the events event event bound to the current element in the view, this method will not be used generally
    // Unless the delegateEvents method is called to rebind the event for the element in the view, the current event will be cleared before rebinding
    // Or reset the attempted el element through the setElement method, which will also clear the event of the current element.
    undelegateEvents : function() {

      this.$('.delegateEvents' + );

    },

    // Set initial configuration when instantiating view objects
    // Overwrite the passed configuration into the object's options
    // Copy the same configuration as the viewOptions list in the configuration to the object itself as the object's properties
    _configure : function(options) {

      // If the object itself has a default configuration, use the passed configuration to merge
      if()

        options = _.extend({}, , options);

      // traverse the viewOptions list
      for(var i = 0, l = ; i &lt; l; i++) {

        // attr is the attribute name in viewOptions in sequence
        var attr = viewOptions[i];

        // Copy the same configuration as viewOptions in the options configuration to the object itself as the object's properties
        if(options[attr])

          this[attr] = options[attr];

      }

      // Set the options configuration of the object
       = options;

    },

    // Each view object should have an el element as the rendered element
    // When constructing a view, you can set the el attribute of the object to specify an element
    // If the el is set as a string or DOM object, then create it as a jQuery or Zepto object through the $ method
    // If the el attribute is not set, a mak method is called to create an element based on the passed tagName, id and className
    // (The newly created element will not be added to the document tree, but is always stored in memory. When the processing is completed and it needs to be rendered to the page, it will generally be appended to the document in the rewritten render method or custom method)
    // (If we need to add an element that is not available to the page and need to add some child elements, attributes, styles or events to it, we can create the element to memory first, and then manually render it to the document after completing all operations, which can improve rendering efficiency)
    _ensureElement : function() {

      // If the el attribute is not set, create the default element
      if(!) {

        // Get attributes attributes from object as the default attribute list of newly created elements
        var attrs = getValue(this, 'attributes') || {};

        // Set the id of the new element
        if()

           = ;

        // Set the class of the new element
        if()

          attrs['class'] = ;

        // Create an element through the make method and call the setElement method to set the element to the standard element used by the view
        ((, attrs), false);

      } else {

        // If the el attribute is set, directly call the setElement method to set the el element as the standard element of the view
        (, false);

      }

    }

  });

 

  // Function that implements object inheritance. This function uses inherits internally. Please refer to inherits function
  var extend = function(protoProps, classProps) {

    // child storage has implemented a subclass inherited from the current class (Function)
    // protoProps sets properties in subclass prototype chain
    // classProps sets the static properties of the subclass
    var child = inherits(this, protoProps, classProps);

    // Add the extend function to the subclass, so calling the extend method of the subclass can achieve inheritance of the subclass
     = ;

    // Return the subclass that implements inheritance
    return child;

  };

  // Implement inheritance mechanism for Model, Collection, Router and View classes
   =  =  =  = extend;

 

  // Related to asynchronous interaction with the server
  // -------------

 

  // Define the correspondence between the server interaction method and request type in Backbone
  var methodMap = {

    'create' : 'POST',

    'update' : 'PUT',

    'delete' : 'DELETE',

    'read' : 'GET'

  };

 

  // sync is used to send requests to the server to synchronize data status when operating data in Backbone to establish a seamless connection with the server
  // sync sends the request by default through the $.ajax method of third-party library (jQuery, Zepto, etc.). Therefore, if you want to call the state synchronization-related methods, the third-party library needs to support it
  // Backbone defines a set of data formats (JSON) and structures that interact with the server by default. The data responding to the server should follow this convention
  // If the data does not need to be saved on the server, or interacts with the server, and the data format structure is inconsistent with the convention, it can be implemented by overloading the sync method
  // @param {String} method CRUD operation name performed in Backbone
  // @param {Model Obejct} model model model with state that needs to be synchronized with the server
  // @param {Object} options

   = function(method, model, options) {

    // Define the method to interact with the server according to the CRUD method name (POST, GET, PUT, DELETE)
    var type = methodMap[method];

 

    // options default to an empty object
    options || ( options = {});

 

    // params passes the $.ajax method to the third-party library as a request parameter object
    var params = {

      // Request type
      type : type,

      // The data format is json by default
      dataType : 'json'

    };

 

    // If the URL address is not set in options when sending the request, the URL will be obtained through the URL attribute or method of the model object.
    // The method of obtaining the url by the model can refer to the url method of the model
    if(!) {

      // When the request address fails to obtain, the urlError method will be called to throw an error
       = getValue(model, 'url') || urlError();

    }

 

    // If the create and update methods are called and the requested data is not defined in options, the data object in the serialized model is passed to the server
    if(! &amp;&amp; model &amp;&amp; (method == 'create' || method == 'update')) {

      // Define the Content-Type header for the request, default is application/json
       = 'application/json';

      // Serialize the data in the model and pass it to the server as requested data
       = (());

    }

 

    // For browsers that do not support application/json encoding, compatibility can be achieved by setting the parameter to true
    if() {

      // Browsers that do not support encoding, set the type to application/x-www-form-urlencoded
       = 'application/x-www-form-urlencoded';

      // Storing the data that needs to be synchronized in the key "model" parameter to send to the server
       =  ? {

        model : 

      } : {};

    }

 

    // For browsers that do not support REST, you can set the parameter to true, send data in POST, and add the _method parameter to the data to identify the operation name
    // At the same time, the X-HTTP-Method-Override header information will also be sent
    if() {

      // If the operation type is PUT or DELETE
      if(type === 'PUT' || type === 'DELETE') {

        // Storing the operation name to the _method parameter to the server
        if()

          ._method = type;

        // In fact, submit it in POST mode, and send the X-HTTP-Method-Override header information
         = 'POST';

         = function(xhr) {

          ('X-HTTP-Method-Override', type);

        };

      }

    }

 

    // Non-GET requests will not convert the data, because the passed data may be a JSON map
    if( !== 'GET' &amp;&amp; !) {

      // Turn off data conversion by setting processData to false
      // The processData parameter is a configuration parameter in the $.ajax method. For details, please refer to jQuery or Zepto related documents.
       = false;

    }

 

    // Send requests to the server to synchronize data status through the $.ajax method of the third-party library
    // The parameters passed to the $.ajax method use the extend method to overwrite the parameters in the options object to the params object. Therefore, when calling the sync method, the options parameter with the same name as the params is set, and the options will be subject to the options.
    return $.ajax(_.extend(params, options));

  };

  // Package a unified model error handling method, which will be called when an error occurs when an error occurs between the model and the server.
  // onError is a custom error handling function specified in options when calling interactive methods with the server (such as fetch, destory, etc.)
  // originalModel is the model or collection object where the error occurred
   = function(onError, originalModel, options) {

    return function(model, resp) {

      resp = model === originalModel ? resp : model;

 

      if(onError) {

        // If a custom error handling method is set, the custom method is called
        onError(originalModel, resp, options);

      } else {

        // By default, the error event of the model or collection that occurred in the error will be triggered.
        ('error', originalModel, resp, options);

      }

    };

  };

  // Helpers define some help functions for use internally in Backbone
  // -------

 

  // ctor is a shared empty function, used to carry the prototype chain of the parent class to set it into the subclass prototype when calling the inherits method to achieve inheritance.
  var ctor = function() {

  };

  // Implement OOP inheritance features
  // @param {Function} parent inherited parent class Function
  // @param {Object} protoProps Extend property (or method) objects in subclass prototype
  // @param {Object} staticProps Extends a static property (or method) object of a subclass
  var inherits = function(parent, protoProps, staticProps) {

    var child;

 

    // If the "constructor" property is specified in protoProps, the "constructor" property is used as a constructor of the subclass
    // If the constructor of the subclass is not specified, the constructor of the parent class is called by default
    if(protoProps &amp;&amp; ('constructor')) {

      // Subclass constructor specified using the "constructor" property
      child = ;

    } else {

      // Use the constructor of the parent class
      child = function() {

        (this, arguments);

      };

    }

 

    // Copy the static attributes in the parent class to the static attributes of the child class
    _.extend(child, parent);

 

    // Set the parent class prototype chain into the prototype object of the subclass, and the subclass inherits all attributes in the parent class prototype chain.
     = ;

     = new ctor();

 

    // Copy the properties in the protoProps object to the prototype object of the subclass, so that the subclass can have the properties in protoProps
    if(protoProps)

      _.extend(, protoProps);

 

    // Copy the properties in the staticProps object to the constructor of the subclass itself, and use the properties in staticProps as the static property of the subclass
    if(staticProps)

      _.extend(child, staticProps);

 

    // When copying the parent class prototype chain to the subclass prototype, the constructor in the subclass prototype chain has been overwritten, so the constructor of the subclass is reset here
     = child;

 

    // If the subclass has the constructor attribute set, the subclass constructor is the function specified by the constructor
    // If you need to call the parent class constructor in the subclass constructor, you need to manually call the parent class constructor in the subclass constructor
    // Here we point the __super__ attribute of the subclass to the constructor of the parent class, which is convenient for calling in the subclass: subclass.__super__.(this);
    child.__super__ = ;

 

    // Return to subclass
    return child;

  };

  // Get the value of the object prop attribute. If the prop attribute is a function, execute and return the return value of the function.
  var getValue = function(object, prop) {

    // If the object is empty or the object does not have a prop attribute, return null
    if(!(object &amp;&amp; object[prop]))

      return null;

    // Return the prop attribute value. If prop is a function, execute and return the return value of the function
    return _.isFunction(object[prop]) ? object[prop]() : object[prop];

  };

  // Throw an Error exception, which will be executed frequently within Backbone, so it is independently a public function
  var urlError = function() {

    throw new Error('A "url" property or function must be specified');

  };

}).call(this);
  • Source code comments
  • Chinese

Related Articles

  • Explain the MVC structure design concept of JavaScript framework

    This article mainly introduces the MVC structure design concept of JavaScript framework. Compared with Backbone, which also has an MVC structure, it seems much lighter. Friends who need it can refer to it.
    2016-02-02
  • Detailed explanation of the model model and its collection collection in the framework

    This article mainly introduces the model model and its collection collection in the framework. Backbone has a Model and View structure similar to the traditional MVC framework. Friends who need it can refer to it.
    2016-05-05
  • Some suggestions for using JavaScript framework

    This article mainly introduces some suggestions for using JavaScript framework. The points listed in the article are mainly aimed at DOM operations. Friends who need it can refer to it.
    2016-02-02
  • Examples explain the View view in JavaScript framework

    This article mainly introduces the examples of the View view in the JavaScript framework. In the browser js framework backbone, we use view to bind and handle DOM events. Friends who need it can refer to it.
    2016-05-05
  • Some tips for using

    This article mainly introduces some usage techniques. It is a popular JavaScript library. Friends who need it can refer to it.
    2015-07-07
  • Detailed explanation of the collection in

    This article mainly introduces the detailed explanation of the collections. This article focuses on the relationship between the collections and other components. Friends who need it can refer to it.
    2015-01-01
  • In-depth analysis of event mechanisms in JavaScript framework

    This article mainly introduces the event mechanism in the JavaScript framework, which involves a lot of knowledge about Backbone's MVC structure and memory usage. Friends who need it can refer to it
    2016-02-02
  • Hello World Program Example

    This article mainly introduces Hello World program examples. This article provides backend PHP communication, front-end HTML, and code. Friends who need it can refer to it.
    2015-06-06
  • Three ways to communicate between Backbone Views

    There are three types of communication between Backbone Views, namely, events are passed through the parent view, and events are communicated between views through EventBus. Backbone is directly used as the event registration machine. This article introduces these three communication methods in detail one by one. Interested friends will learn together.
    2016-08-08
  • JavaScript framework environment construction and Hello world example

    This article mainly introduces the JavaScript framework environment construction and Hello world example. Backbone is a front-end MVVM framework similar to MVC structure. It is very lightweight. Friends who need it can refer to it.
    2016-05-05

Latest Comments