jQuery source code analysis DOM operation module insert element details

The DOM operation module of jQuery encapsulates the native methods of DOM model, such as insertBefore(), appendChild(), removeChild(), cloneNode(), replaceChild(). It is divided into five sub modules to realize: insert element, delete element, copy element, replace element and wrap element. This section explains the first sub module: insert element

The insert element module can be used to add DOM nodes, modify text nodes, etc. the API is as follows:

  • append(content); insert the specified content at the end of the selected element sub node, and call the appendChild(elem) method internally; content can be HTML code, function (return HTML code), DOM element or jQuery object, the same below
  • prepend(content); inserts the specified content in the header of the selected element sub node
  • before(content); inserts content before each element of the set of matching elements.
  • after(content);, insert content after each element of the matching element set
  • appendTo(target) inserts each element in the matching element into the end of the target element; internally calls the append() method; after the execution of these four methods, the pushStack() method will be called, matching all the DOM object references inserted.
  • prependTo(target); inserts each element in the matching element at the beginning of the target element; calls the prepeng() method internally
  • insertBefore(target); inserts each element in the matching element before the target element; calls the before() method internally
  • insertAfter(target); insert each element in the matching element after the target element; internally call the after() method

The first four and the last four are one-to-one, for example:

writer by: Desert QQ:22969969

$('#d').append('<p>1</p>')       
$('<p>1</p>').appendTo($('#d'));      //The two codes work the same

The other API s work the same way: prepend and prependTo, before and insertBefore, after and insertAfter are all the same.

Here's a chestnut:

<!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>
    <div id="d">
        <p id="p">Hello,World!</p>
    </div>
    <h1>insertBefore test</h1>
    <h2>insertAfter test</h2>
    <button id="b1">append test</button>
    <button id="b2">prepend test</button>
    <button id="b3">before test</button>
    <button id="b4">insertAfter test</button>
    <script>    
        $('#b1').click(()=>{                
            $('#d').append('<p>append test</p>')        //stay div Add a at the end of the child node of the element<p>1</p>Node, the parameter here is a html Code
        })
        $('#b2').click(()=>{
            $('#d').prepend('<p>prepend test</p>')      //stay div Add a<p>2</p>Node, the parameter here is a html Code
        })
        $('#b3').click(()=>{
            $('h1').insertBefore('#d')                 //stay div Add before element element $('h1')Element, where the argument is a jQuery object
        })  
        $('#b4').click(()=>{
            $('h2').insertAfter($('#d'))                //stay div Add after element element $('h2')Element, the parameter here is also a jQuery object
        })
    </script>
</body>
</html>

Render as follows:

The corresponding DOM tree is as follows:

 

We have defined four buttons, which are respectively in front of the child node of div and by default. A DOM element is inserted in the position before div and after Div. click each button once, and the page after execution is as follows:

 

The corresponding DOM tree is as follows:

 

Source code analysis

The implementation of append, prepend, before and after is as follows:

jQuery.fn.extend({
    append: function() {        //Insert the specified content at the end of the selected element prime sub node, internal call appendChild(elem)Method
        return this.domManip(arguments, true, function( elem ) {
            if ( this.nodeType === 1 ) {
                this.appendChild( elem );
            }
        });
    },

    prepend: function() {        //Insert the specified content in the head of the selected prime sub node, internal call insertBefore( elem, this.firstChild )Method
        return this.domManip(arguments, true, function( elem ) {
            if ( this.nodeType === 1 ) {
                this.insertBefore( elem, this.firstChild );
            }
        });
    },

    before: function() {        //Insert content before matching each element of the element collection
        if ( this[0] && this[0].parentNode ) {            //If the element has a parent
            return this.domManip(arguments, false, function( elem ) {
                this.parentNode.insertBefore( elem, this );
            });
        } else if ( arguments.length ) {
            var set = jQuery.clean( arguments );
            set.push.apply( set, this.toArray() );
            return this.pushStack( set, "before", arguments );
        }
    },
    after: function() {            //Insert content after each element of the matching element collection
        if ( this[0] && this[0].parentNode ) {             //If the element has a parent
            return this.domManip(arguments, false, function( elem ) {
                this.parentNode.insertBefore( elem, this.nextSibling );
            });
        } else if ( arguments.length ) {
            var set = this.pushStack( this, "after", arguments );
            set.push.apply( set, jQuery.clean(arguments) );
            return set;
        }
    },
    /*slightly*/
})

It can be seen that the function of domManip is invoked inside, and the first one is passed to arguments when executing. That is, we call the parameters introduced by append and prepend. DomManip transforms the parameters into a DOM object, then calls parameter 3, parameter 3 is responsible for performing the last DOM operation. DomManip is implemented as follows:

jQuery.fn.extend({
    domManip: function( args, table, callback ) {            //jQuery Insert the core of element implementation, responsible for transformation html Code for DOM Element, and then call the incoming callback function to insert. DOM element
        var results, first, fragment, parent,
            value = args[0],                                    //for example $().append()First parameter of,such as:,<p>123</p>
            scripts = [];                                        //storage args[0]Li script Script

        // We can't cloneNode fragments that contain checked, in WebKit
        if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
            return this.each(function() {
                jQuery(this).domManip( args, table, callback, true );
            });
        }

        if ( jQuery.isFunction(value) ) {                        //If args[0]If it is a function, traverse the set of matching elements and execute args[0]Function, and take the return value as a parameter to traverse the execution again domManip()function
            return this.each(function(i) {
                var self = jQuery(this);    
                args[0] = value.call(this, i, table ? self.html() : undefined);        //Execute the function on each element and return a DOM element
                self.domManip( args, table, callback );                                    //Iterative call.domMapnip()Method
            });
        }

        if ( this[0] ) {                                        //If there is at least one matching element
            parent = value && value.parentNode;

            // If we're in a fragment, just use that instead of building a new one
            if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
                results = { fragment: parent };                            //At 1.7.1 in jQuery.support.parentNode Method is not defined, so this code will not execute, in 1.8 This code has been removed from.

            } else {
                results = jQuery.buildFragment( args, this, scripts );    //call buildFragment()hold html Code to DOM Element. Returns an object. Object fragment Property contains the converted DOM Element, cacheable Indicates the cache state, indicating the HTML Whether the element can be cached
            }

            fragment = results.fragment;                                //fragment It's converted DOM element

            if ( fragment.childNodes.length === 1 ) {                    //If the document fragment has only one child element
                first = fragment = fragment.firstChild;                        //Reset variables fragment Is a child element, because inserting a single child element is slightly faster than inserting a document fragment with a single child element.
            } else {    
                first = fragment.firstChild;                            //Or else first As the first child node.
            }

            if ( first ) {                                                //If a child node exists
                table = table && jQuery.nodeName( first, "tr" );            //If table Parameter is true,And first yes tr Node, then set table by true,Otherwise, false. 

                for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {    //Traverse the currently matched elements
                    callback.call(                                             //implement callback Function, the context is each matching element, namely callback In function this Pointer. Parameters are converted DOM Element, which is passed to callback The parameter of, that is append,prepend Wait for internal execution doMainip()Passed parameter 3 this function
                        table ?
                            root(this[i], first) :                             //Fix the target object if the element to be inserted is tr Element, the function is called root()Check current element(Container element)Whether it is table Element, return if yes tr Parent element of element tbody As the target element.
                            this[i],                                        //Otherwise, match elements as call The first parameter of this Pointer.
                        // Make sure that we do not leak memory by inadvertently discarding
                        // the original fragment (which might have attached data) instead of
                        // using it; in addition, use the original fragment object for the last
                        // item instead of first because it can end up being emptied incorrectly
                        // in certain situations (Bug #8070).
                        // Fragments from the fragment cache must always be cloned and never used
                        // in place.
                        results.cacheable || ( l > 1 && i < lastIndex ) ?     //Fix the element to be inserted. If the returned document fragment is cacheable or cannot be cached, but the current matching element is greater than 1 and the current operation is not the last matching element of the operation, the copy of the document fragment is always inserted.
                            jQuery.clone( fragment, true, true ) :                 //Always insert a copy of the document fragment,call jQuery.clone Deep replication of events and data
                            fragment                                             //Otherwise insert the document itself
                    ); 
                }
            }

            if ( scripts.length ) {                                         //After performing the conversion DOM Of the target element script element
                jQuery.each( scripts, evalScript );                             //jQuery.buildFragment()and jQuery.clean()Using browser's innerHTML Mechanism to HTML Code to DOM Element, but the converted script Elements are not loaded and executed automatically and need to be handled manually.
            }
        }

        return this;                                             //Returns a collection of matching elements to support chained operations.
    }
    /*slightly*/
})

buildFragment is used to convert html code into the corresponding DOM node. It first attempts to obtain the corresponding DOM object from the cache. If there is no cache, it creates a document fragment through document.createDocumentFragment, and then calls $. clean to convert html code into a DOM object (implemented by setting innerHTML). There are many codes, so it is no longer pasted.

For appendTo, prependTo, insertBefore and insertAfter, they will define a temporary jQuery object internally, and then call the corresponding append, prepend, before and after to implement, as follows:

jQuery.each({
    appendTo: "append",
    prependTo: "prepend",
    insertBefore: "before",
    insertAfter: "after",
    replaceAll: "replaceWith"
}, function( name, original ) {
    jQuery.fn[ name ] = function( selector ) {        //to jQuery Add to appendTo,prependTo,insertBefore,insertAfter,replaceAll Method    name Is the name of the method to add, such as appendTo,original Identify the corresponding existing method name, such as append
        var ret = [],         
            insert = jQuery( selector ),                            //Construct a jQuery Object, pointing to the collection of target elements, which is the internal temporary jQuery object
            parent = this.length === 1 && this[0].parentNode;        //If the html If there is only one top-level node, the parent Set to parent element reference, otherwise set to false     ;such as:$('<p>222</p>').appendTo('#dd');

        if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {    //If there is only one element to be inserted, and it is in the document fragment, and there is only one target element,
            insert[ original ]( this[0] );                                                                        //Then insert the element to be inserted directly into the target element without traversing the target element collection. It's a little faster.
            return this;

        } else {
            for ( var i = 0, l = insert.length; i < l; i++ ) {                //Traversing the set of target elements, cycling inside the body insert[i]Is every target element
                var elems = ( i > 0 ? this.clone(true) : this ).get();        //elems Is the collection of elements to be inserted. The first one is the collection of elements to be inserted, and the next one is a copy of it. The corresponding method name of the call has been inserted elems element
                jQuery( insert[i] )[ original ]( elems );                        //Calling the original Method, parameter is elems        
                ret = ret.concat( elems );
            }

            return this.pushStack( ret, name, insert.selector );            //Use an array containing the collection of elements to be inserted and its copies ret Construct a new jQuery Object and return.
        }
    };
});

From the code, we can see that appendTo, prependTo, insertBefore and insertAfter are just a package of append, prepend, before and after.

Tags: Javascript JQuery Fragment

Posted on Tue, 05 Nov 2019 19:04:29 -0800 by smti