jQuery Source Code Analysis data Caching Module

Posted by jenni on Mon, 14 Oct 2019 01:43:21 +0200

jQuery's data caching module attaches any type of data to DOM elements in a secure way, avoiding circular references between JavaScript objects and DOM elements and resulting memory leaks.

Data caching module provides a unified method of data setting, reading and removing for DOM elements and JavaScript objects. Within jQuery, it also provides basic functions for queue module, animation module, style operation module and event system. It is responsible for maintaining the internal data of these modules when they are running.

Writby: Great Desert QQ:22969969

For DOM elements and JavaScript objects, the location of data storage is different, as follows:

  • For DOM elements jQuery stores data directly in jQuery.cache
  • For JavaScript objects, garbage collection can occur automatically, so data can be stored directly in JavaScript objects.

In addition, in order to avoid conflict between data used inside jQuery and user-defined data, it is divided into internal data caching object and custom data caching object.

  • Internal cached objects; jQuery internal use; DOM elements: stored in $. cache[elem[$.expando]; JavaScript objects: obj[$.expando]
  • Custom Cache Object; User Used; DOM Element: Stored in $. cache[elem[$.expando]].data; JavaScript Object: obj[$.expando].data.

jQuery's static method contains the following API s:

  • Data caching object for DOM elements, in which all data stored by DOM elements are stored
  • Unique id seed with an initial value of 0, used when data is stored on DOM elements, the value of the element's $. expando attribute is equal to the latest $. uuid plus 1
  • $. expando *; The unique identity of each jQuery copy in a page changes only when the page is refreshed. Format: jQuery + Version Number + Random Number
  • AceptData (elem); Determines whether the DOM element elem can set data, elem is a DOM node
  • hasData(elem); Determine whether elem has associated data
  • data(elem, name, data,pvt); sets or returns data for DOM or JavaScript objects.

· elem is a DOM element or JavaScript object.

· Name is a data name to set or read, or it can be an object containing key-value pairs.

· Data is the data value to be set, which can be arbitrary data.

· pvt denotes whether the operation is internal data or not, and defaults to false

  • $. _data(elem, name, data); set, read internal data, internal code on a return jQuery. data (elem, name, data, true)
  • removeData(elem, name, pvt); removes data set through $. data(), which indicates whether or not it is internal data
  • Clean Data (elem); Remove all data and events from multiple DOM elements

JQuery /$instance method (which can be invoked through a jQuery instance):

  • data(key,value); Setting/reading custom data
  • removeData(key); removes custom data for matching elements, keys can be a character exchange or an array representing a list of attributes or attributes

When storing data for a DOM element, jQuery first adds an attribute named $. expando to the DOM with a unique id equal to ++$. uuid (a built-in attribute of jQuery), $. uuid is an integer value with an initial value of 0. After adding attributes to the DOM, the id is added to the global caching object jQuery.cache as an attribute. The corresponding attribute value is a JavaScript object, which is the data caching object of the DOM element.

For example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="http://libs.baidu.com/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
    <p>123</p>
    <script>
        var p = document.getElementsByTagName('p')[0];
        $.data(p,'age',25,true);                                //Set internal data age=25,This is directly defined on the data cache object. Equivalent to $._data(p,'age',25);
        $.data(p,'age',23);                                     //Setting up custom data age=23,Equivalent to $.data(p,'age',23,false),This is defined in the data cache object data On attribute objects
        console.log($.data(p,undefined,undefined,true));        //output: Object { data={ age=23},  age=25}    ;Get the data cache object.    
        console.log($.data(p));                                 //output: Object { age=23}             ;Getting custom cache objects,In fact, that is $.data(p,undefined,undefined,true)Object data attribute
        console.log($.cache[p[$.expando]].data === $.data(p));  //output true,From this we can see that $.data(p)What you get is a custom cache object, that is, a data cache object. data Attribute object
    </script>    
</body>
</html>

The output is as follows:

 

Source code analysis

For the static method of data caching module, it is mounted directly into jQuery with jQuery.extend({}) function, as follows:

jQuery.extend({
  cache: {},                                //DOM Data caching objects for elements

  // Please use with caution
  uuid: 0,

  // Unique for each copy of jQuery on the page
  // Non-digits removed to match rinlinejQuery
  expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),    //Each of the pages jQuery Unique Identification of Copies

  // The following elements throw uncatchable exceptions if you
  // attempt to add expando properties to them.
  noData: {                                   //Stores properties that do not support extension embed,object,applet Node name of element
    "embed": true,
    // Ban all objects except for Flash (which handle expandos)
    "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
    "applet": true
  },

  hasData: function( elem ) {                 //Judge one DOM Element or JavaScript Does an object have data associated with it?
    elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];     //If it is an element node(Yes nodeType attribute)Then judge jQuery.cache Are there any jQuery.expando Attribute, otherwise considered JavaScript Object, judge whether there is jQuery.expando Attribute.
    return !!elem && !isEmptyDataObject( elem );                                              //If elem Returns if there is and contains a data cache true,isEmptyDataObject It's a jQuery Internal tool functions
  },

  data: function( elem, name, data, pvt /* Internal Use Only */ ) {
    /*slightly*/
  },

  removeData: function( elem, name, pvt /* Internal Use Only */ ) {
    /*slightly*/
  },

  // For internal use only.
  _data: function( elem, name, data ) {       //Setting and reading internal data is called jQuery.data(),And set the fourth parameter to true
    return jQuery.data( elem, name, data, true );
  },

  // A method for determining if a DOM node can handle the data expando
  acceptData: function( elem ) {              //Judgement parameter elem Can you set the data and return it? true It can be set and returned false You can't do that.
    if ( elem.nodeName ) {
      var match = jQuery.noData[ elem.nodeName.toLowerCase() ];

      if ( match ) {
        return !(match === true || elem.getAttribute("classid") !== match);
      }
    }

    return true;
  }
});

Let's mainly look at how $. data() sets up the data, and if we understand how to set up the data, removeData will understand, as follows:

data: function( elem, name, data, pvt /* Internal Use Only */ ) {       //Setting, reading custom data, internal data
  if ( !jQuery.acceptData( elem ) ) {               //inspect elem Whether the element supports setting data, if jQuery.acceptData()Function return false Indicates that data is not allowed to be set
    return;                                           //It returns directly and does not continue to operate.
  }

  var privateCache, thisCache, ret,                 //privateCache Default point to data cache object(If pvt The parameter is not set or is false Point to custom data),thisCache Represents custom data cache objects,If pvt yes true,be privateCache and thisCache All point to data cache objects and all point to data cache objects. ret Is the return value at read time
    internalKey = jQuery.expando,                   //jQuery.expando Each of the pages jQuery The unique identifier of the copy, assigning it to internalKey To reduce spelling and shorten scope chain lookup.
    getByName = typeof name === "string",           //getByName Express name Is it a string?

    // We have to handle DOM nodes and JS objects differently because IE6-7
    // can't GC object references properly across the DOM-JS boundary
    isNode = elem.nodeType,                         //isNode Express elem Whether it is DOM element

    // Only DOM nodes need the global jQuery cache; JS object data is
    // attached directly to the object so GC can occur automatically
    cache = isNode ? jQuery.cache : elem,           //If it is DOM Elements are stored in $.cache If yes JavaScript The object is stored in the object itself. 

    // Only defining an ID for JS objects if its cache already exists allows
    // the code to shortcut on the same path as a DOM node with no cache
    id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
    isEvents = name === "events";

  // Avoid doing any more work than we need to when trying to get data on an
  // object that has no data at all
  if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {   //If the data is read but no data is available, it is returned to avoid unnecessary work.,if The coincidence expression in a statement can be divided into two parts, the latter part getByName && data === undefined,Represents that if name Is a string and data If no settings are set, it means that the data is being read. Former part(!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)Represents that if id There is no indication that there is no attribute if cache[id]The absence of this data means that there is no such data.
    return;
  }
  /*There are two situations: 1. Storing data; 2. Reading data and data existence.*/   
  if ( !id ) {                                      //If id If it does not exist, assign one
    // Only DOM nodes need a new unique ID for each element since their data
    // ends up in the global cache
    if ( isNode ) {                                   //If it is DOM element
      elem[ internalKey ] = id = ++jQuery.uuid;         //jQuery.uuid It will automatically add 1 and attach it to the DOM Element
    } else {
      id = internalKey;                               //Otherwise associated ID Namely jQuery.expando
    }
  }

  if ( !cache[ id ] ) {                           //If DOM Object or JavaScript If the corresponding data cache object does not exist, it is initialized as an empty object.
    cache[ id ] = {};

    // Avoids exposing jQuery metadata on plain JS objects when the object
    // is serialized using JSON.stringify
    if ( !isNode ) {
      cache[ id ].toJSON = jQuery.noop;
    }
  }

  // An object can be passed to jQuery.data instead of a key/value pair; this gets
  // shallow copied over onto the existing cache
  if ( typeof name === "object" || typeof name === "function" ) { //If name It's an object or a function.(It seems that a function is not possible, it can only be an object.),Then batch parameters name The attributes in the cache are merged into existing data caching objects, i.e. batch setting data
    if ( pvt ) {
      cache[ id ] = jQuery.extend( cache[ id ], name );
    } else {
      cache[ id ].data = jQuery.extend( cache[ id ].data, name );
    }
  }

  privateCache = thisCache = cache[ id ];                         //Set up privateCache and thisCache All point to data cache objects cache[ id ]

  // jQuery data() is stored in a separate object inside the object's internal data
  // cache in order to avoid key collisions between internal data and user-defined
  // data.
  if ( !pvt ) {                                                   //If parameters pvt yes false Or if it is not set, it is set thisCache Point to custom data,
    if ( !thisCache.data ) {                                        //If the data cache object thisCache.data If it does not exist, it is initialized to an empty object.
      thisCache.data = {};
    }

    thisCache = thisCache.data;
  }

  if ( data !== undefined ) {                                     //If data No undefined,Then the parameters data Set to Properties name On the other hand, the parameters are unified here. name Converted to hump, so that when reading, whether it is a hyphen or hump, there will be no error.
    thisCache[ jQuery.camelCase( name ) ] = data;
  }

  // Users should not attempt to inspect the internal events object using jQuery.data,
  // it is undocumented and subject to change. But does anyone listen? No.
  if ( isEvents && !thisCache[ name ] ) {
    return privateCache.events;
  }

  // Check for both converted-to-camel and non-converted data property names
  // If a data property was specified
  if ( getByName ) {                                            //If parameters name Is a string, then read a single data

    // First Try to find as-is property data
    ret = thisCache[ name ];                                      //First try to read the parameters name Corresponding data

    // Test for null|undefined property data  
    if ( ret == null ) {                                          //If it is not read, the parameter is read name Convert to Hump and try to read again

      // Try to find the camelCased property
      ret = thisCache[ jQuery.camelCase( name ) ];
    }
  } else {
    ret = thisCache;                                          //If parameter 2 is not a string,Returns the data cache object.
  }

  return ret;                                                 //Last return ret
},

This completes the setup of the data. For the jQuery instance, the method is as follows:

jQuery.fn.extend({
  data: function( key, value ) {      //Setting, reading and parsing custom data html5 attribute data- key To set or read data names, or objects with key-value pairs, value The data value to be set can be of any type
    var parts, attr, name,
      data = null;

    if ( typeof key === "undefined" ) {       //If no parameter is passed in, the parameter format is.data(),Get the data cache object associated with the first matching element(That is, to get all the data.)
      if ( this.length ) {                      //If this jQuery Objects have matching elements
        data = jQuery.data( this[0] );            //Get the data cache object for the first element

        if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {    //Here is the analysis. html5 Li data-Attribute, can be skipped first
          attr = this[0].attributes;
          for ( var i = 0, l = attr.length; i < l; i++ ) {
            name = attr[i].name;

            if ( name.indexOf( "data-" ) === 0 ) {
              name = jQuery.camelCase( name.substring(5) );

              dataAttr( this[0], name, data[ name ] );
            }
          }
          jQuery._data( this[0], "parsedAttrs", true );   //Returns a custom data cache object associated with the first matching element. If there is no matching element, it returns null
        }
      }

      return data;

    } else if ( typeof key === "object" ) {         //If key Is an object, calling methods for each element object $.data(this,key)Batch setup data
      return this.each(function() {
        jQuery.data( this, key );
      });
    }

    parts = key.split(".");
    parts[1] = parts[1] ? "." + parts[1] : "";      //Take out namespaces, such as $(this).data('a.b',123);be parts[1]yes.b

    if ( value === undefined ) {                    //If the incoming format is.data(key),Think of it as reading individual data.
      data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);     //Triggering custom events getData,And assign the return value of the event listener function to the variable data

      // Try to fetch any internally stored data first
      if ( data === undefined && this.length ) {          //If the event listener function does not return a value, it will attempt to read from the custom data cache object.
        data = jQuery.data( this[0], key );
        data = dataAttr( this[0], key, data );
      }

      return data === undefined && parts[1] ?             //If from getData()Event listener functions or custom data cache objects or HTML5 attribute data-When the data is retrieved, the data is returned.;If no data is fetched, but a namespace is specified, the namespace is removed and read again.
        this.data( parts[0] ) :
        data;

    } else {                                          //If a parameter is passed in key and value,That is, the parameter format is:.data(key,value),Set any type of data for each matching element and trigger custom events setData()and changeData(). 
      return this.each(function() {
        var self = jQuery( this ),
          args = [ parts[0], value ];

        self.triggerHandler( "setData" + parts[1] + "!", args );        //Triggering custom events setData,Exclamation marks indicate that only event listeners without named controls are executed
        jQuery.data( this, key, value );                                //call $.data()Method to set any type of data for any matching element
        self.triggerHandler( "changeData" + parts[1] + "!", args );     //Triggering custom events changeData
      });
    }
  },
  /*...*/
})

This is how to set up data cache. If you understand how to set up data cache, you can easily understand how to remove it.

Topics: Javascript JQuery Attribute html5