domenica 7 aprile 2013

Decoding jQuery – domManip: DOM Manipulation

In the Decoding jQuery series, we will break down every single method in jQuery, to study the beauty of the framework, as an appreciation to the collective/creative geniuses behind it.



Inside manipulation.js within the jQuery source, there is an interesting internal method named

domManip, this is mainly used for methods like: .append(), .prepend(), .before() and .after().
domManip: function( args, table, callback ) {
  //...
}
 
Using fragment
In domManip, jQuery first checks if we are dealing with fragment node. In DOM, there are 12 different node types, and the one jQuery is checking is type 11 (Node.DOCUMENT_FRAGMENT_NODE == 11).
If we’re in a fragment, just use it, otherwise, building a new one using jQuery.buildFragment.
 
if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
    results = { fragment: parent };
} else {
    results = jQuery.buildFragment( args, this, scripts );
}
 
jQuery.buildFragment
Let’s take out jQuery.buildFragment and see what it does. The method comes with excellent explanation that talks about what each block of code does:

1. nodes may contain either an explicit document object, a jQuery collection or context object. The following checks if nodes[0] contains a valid object to assign to doc

if ( nodes && nodes[0] ) {
  doc = nodes[0].ownerDocument || nodes[0];
}
 
2. Ensure that an attr object doesn’t incorrectly stand in as a document object, Chrome and Firefox seem to allow this to occur and will throw exception
if ( !doc.createDocumentFragment ) {
  doc = document;
}
With the above check, the script will then fragment
if ( !fragment ) {
  fragment = doc.createDocumentFragment();
  jQuery.clean( args, doc, fragment, scripts );
}
 
3. To check if an element is cacheable. The method only caches “small” (1/2 KB) HTML strings that are associated with the main document. Cloning options loses the selected state, so it doesn’t cache them. IE 6 doesn’t like it when you put object or embed elements in a fragment. Also, WebKit does not clone ‘checked’ attributes on cloneNode, so it doesn’t cache them either. Lastly, IE6, 7, 8 will not correctly reuse cached fragments that were created from unknown elements.

if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
    first.charAt(0) === "<" && !rnocache.test( first ) &&
    (jQuery.support.checkClone || !rchecked.test( first )) &&
    (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
 
    cacheable = true;
 
    cacheresults = jQuery.fragments[ first ];
    if ( cacheresults && cacheresults !== 1 ) {
      fragment = cacheresults;
    }
  }
 
The following script does the caching once it’s determined cacheable:

if ( cacheable ) {
  jQuery.fragments[ first ] = cacheresults ? fragment : 1;
}
 
Appending script
I first discovered this while inserting a script tag using jQuery and inspect the element without seeing this. This is because within the domManip, there is a check for script elements.

if ( scripts.length ) {
  jQuery.each( scripts, function( i, elem ) {
    if ( elem.src ) {
      jQuery.ajax({
        type: "GET",
        global: false,
        url: elem.src,
        async: false,
        dataType: "script"
      });
    } else {
      jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
    }
 
    if ( elem.parentNode ) {
      elem.parentNode.removeChild( elem );
    }
  });
}
 
This check does 4 things here:
1. first it detects and loop through all the script elements
2. if the script element has a src attribute, then jQuery uses jQuery ajax to load the script
3. if the script is embedded, jQuery uses jQuery.globalEval to evaluate the script
4. it removed the child from the parent element

Nessun commento:

Posta un commento