Learn the overall architecture of jQuery source code and build your own js class library

Keywords: Javascript JQuery Vue Mac Windows

Although jQuery is hardly used now, it is still necessary to learn its source code for JS libraries that have been popular for more than 10 years. You can also learn to build your own JS class library, job interviews can add a lot of color.

This article is about v3.4.1.
unpkg.com source address: https://unpkg.com/jquery@3.4....

jQuery github repository

self-executing anonymous function

(function(global, factory){

})(typeof window !== "underfined" ? window: this, function(window, noGlobal){

});

External variables and functions can not be accessed by the outside world. External variables can be accessed by the inside, but the variables defined by the inside will not be accessed by the outside world.
Anonymous functions wrap code inside to prevent conflicts with other code and pollution of the global environment.
Readers who don't know much about self-executing functions can refer to this article.
[[Translated] JavaScript: Execute Functional Expressions (IIFE)]( https://segmentfault.com/a/11...

In the browser environment, the $and jQuery functions are finally mounted on window s, so you can access the $and jQuery outside.

if ( !noGlobal ) {
    window.jQuery = window.$ = jQuery;
}
// The `noGlobal'parameter is only used here.

Support for use in multiple environments such as commonjs, amd specifications

commonjs specification support

nodejs is the main representative of commonjs implementation

// Global is a global variable and factory is a function.
( function( global, factory ) {

    //  Use strict mode
    "use strict";
    // Commonjs or CommonJS-like environment
    if ( typeof module === "object" && typeof module.exports === "object" ) {
        // If global.document exists, return factory(global, true);
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }

// Pass this if window is not defined yet
// The first parameter determines window, there is a return window, there is no return this
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {});

amd specification mainly represents requirejs

if ( typeof define === "function" && define.amd ) {
    define( "jquery", [], function() {
        return jQuery;
    } );
}

cmd specification mainly represents seajs

Unfortunately, support for seajs is not exposed in the jQuery source code. But there are also some solutions on the Internet. There is no specific mention here. After all, seajs is hardly needed now.

No new structure

Actually, it's also new, because jQuery is a function. And the effect is the same as that without new.
new displays the returned object, so it works the same way as calling the jQuery function directly.
If you don't understand exactly what the new operator does. See my previous article.

Interviewer asked: Can you simulate the new operator that implements JS?

Source code:

 var
    version = "3.4.1",

    // Define a local copy of jQuery
    jQuery = function( selector, context ) {
        // Object after returning new
        return new jQuery.fn.init( selector, context );
    };
jQuery.fn = jQuery.prototype = {
    // Current version of jQuery
    jquery: version,
    // Modified constructor to jQuery
    constructor: jQuery,
    length: 0,
};
init = jQuery.fn.init = function( selector, context, root ) {
    // ...
    if ( !selector ) {
        return this;
    }
    // ...
};
init.prototype = jQuery.fn;
jQuery.fn === jQuery.prototype;     // true
init = jQuery.fn.init;
init.prototype = jQuery.fn;
// that is
jQuery.fn.init.prototype === jQuery.fn;     // true
jQuery.fn.init.prototype === jQuery.prototype;     // true

About this, the author draws a jQuery prototype diagram, the so-called picture is worth a thousand words.

<sciprt src="https://unpkg.com/jquery@3.4.1/dist/jquery.js">
</script>
console.log({jQuery});
// In the Google Browser console, you can see that many static attributes and methods are mounted under the jQuery function, and many attributes and methods are also mounted on jQuery.fn.

In the Vue source code, similar to jQuery, the Vue.prototype._init method is executed.

function Vue (options) {
    if (!(this instanceof Vue)
    ) {
        warn('Vue is a constructor and should be called with the `new` keyword');
    }
    this._init(options);
}
initMixin(Vue);
function initMixin (Vue) {
    Vue.prototype._init = function (options) {};
};

extend, one of the core functions

Usage:

jQuery.extend( target [, object1 ] [, objectN ] )        Returns: Object

jQuery.extend( [deep ], target, object1 [, objectN ] )

jQuery.extend API
jQuery.fn.extend API

Look at a few examples:
(Examples can be put in the online editing code jQuery.extend example codepen Yes, it can run directly.

// 1. jQuery.extend( target)
var result1 = $.extend({
    job: 'Front-end Development Engineer',
});

console.log(result1, 'result1', result1.job); // The $function adds an attribute job // front-end development engineer

// 2. jQuery.extend( target, object1)
var result2 = $.extend({
    name: 'If Chuan',
},
{
    job: 'Front-end Development Engineer',
});

console.log(result2, 'result2'); // {name:'Ruochuan', job:'front-end development engineer'}

// deep copy
// 3. jQuery.extend( [deep ], target, object1 [, objectN ] )
var result3 = $.extend(true,  {
    name: 'If Chuan',
    other: {
        mac: 0,
        ubuntu: 1,
        windows: 1,
    },
}, {
    job: 'Front-end Development Engineer',
    other: {
        mac: 1,
        linux: 1,
        windows: 0,
    }
});
console.log(result3, 'result3');
// deep true
// {
//     "name": "Ruochuan".
//     "other": {
//         "mac": 1,
//         "ubuntu": 1,
//         "windows": 0,
//         "linux": 1
//     },
//     "job": "front-end development engineer"
// }
// deep false
// {
//     "name": "Ruochuan".
//     "other": {
//         "mac": 1,
//         "linux": 1,
//         "windows": 0
//     },
//     "job": "front-end development engineer"
// }

Conclusion: The extended function can realize both shallow copy and deep copy for jQuery function. You can add static methods and attributes to jQuery, or you can add attributes and methods to jQuery.fn (that is, jQuery.prototype). This function is attributed to this. This point is jQuery when jQuery.extend is invoked, and this point is jQuery.fn.extend when jQuery.fn.extend is invoked.

Shallow Copy Realization

Knowing this, it's easy to make shallow copies:

// Shallow Copy Realization
jQuery.extend = function(){
    // options is the extended object object1, object2...
    var options,
    // Key on object object object
    name,
    // The value on the copy object object object object, that is, the value that needs to be copied
    copy,
    // Extending the target object may not be an object, so it may be an empty object.
    target = arguments[0] || {},
    // Definition i is 1
    i = 1,
    // Define the number of arguments length
    length = arguments.length;
    // When there is only one parameter
    if(i === length){
        target = this;
        i--;
    }
    for(; i < length; i++){
        // Not underfined or null
        if((options = arguments[i]) !=  null){
            for(name in options){
                copy = options[name];
                // To prevent the dead cycle, continue jumps out of the current cycle
                if ( name === "__proto__" || target === copy ) {
                    continue;
                }
                if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }

    }
    // Finally, return the target object
    return target;
}

The deep copy is mainly judged by the following code. Maybe it's the value of the array and object reference type to make a judgment.

if ( copy !== undefined ) {
    target[ name ] = copy;
}

To facilitate reader debugging, the code is also placed in Implementation of codepen with jQuery. extended shallow copy code It can run online.

Deep copy implementation

$.extend = function(){
    // options is the extended object object1, object2...
    var options,
    // Key on object object object
    name,
    // The value on the copy object object object object, that is, the value that needs to be copied
    copy,
    // Four new variables deep, src, copyIsArray, clone
    deep = false,
    // Source target, which needs to be assigned above
    src,
    // The type of value that needs to be copied is a function
    copyIsArray,
    //
    clone,
    // Extending the target object may not be an object, so it may be an empty object.
    target = arguments[0] || {},
    // Definition i is 1
    i = 1,
    // Define the number of arguments length
    length = arguments.length;

    // Dealing with deep copies
    if ( typeof target === "boolean" ) {
        deep = target;

        // Skip the boolean and the target
        // Target target object starts to move backwards
        target = arguments[ i ] || {};
        i++;
    }

    // Handle case when target is a string or something (possible in deep copy)
    // When target is not equal to an object and target is not a function, it is forced to assign it to an empty object.
    if ( typeof target !== "object" && !isFunction( target ) ) {
        target = {};
    }

    // When there is only one parameter
    if(i === length){
        target = this;
        i--;
    }
    for(; i < length; i++){
        // Not underfined or null
        if((options = arguments[i]) !=  null){
            for(name in options){
                copy = options[name];
                // To prevent the dead cycle, continue jumps out of the current cycle
                if ( name === "__proto__" || target === copy ) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays
                // Here deep is true, and the value that needs to be copied is valuable, and it's a pure object.
                // Or the number to be copied is an array
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
                    ( copyIsArray = Array.isArray( copy ) ) ) ) {

                    // Source target, which needs to be assigned above
                    src = target[ name ];

                    // Ensure proper type for the source value
                    // The value of the copy, and the src is not an array, the clone object is changed to an empty array.
                    if ( copyIsArray && !Array.isArray( src ) ) {
                        clone = [];
                        // Copied values are not arrays, objects are not pure objects.
                    } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
                        // clone assigns empty objects
                        clone = {};
                    } else {
                        // Otherwise clone = src
                        clone = src;
                    }
                    // CopIsArray needs to be reassigned to false for the next loop
                    copyIsArray = false;

                    // Never move original objects, clone them
                    // Recursively invoke oneself
                    target[ name ] = jQuery.extend( deep, clone, copy );

                // Don't bring in undefined values
                }
                else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }

    }
    // Finally, return the target object
    return target;
};

To facilitate reader debugging, this code is also placed in Implementation of codepen with jQuery.extend deep copy code It can run online.

Deep copy derived function isFunction

Determine whether the parameter is a function.

var isFunction = function isFunction( obj ) {

    // Support: Chrome <=57, Firefox <=52
    // In some browsers, typeof returns "function" for HTML <object> elements
    // (i.e., `typeof document.createElement( "object" ) === "function"`).
    // We don't want to classify *any* DOM node as a function.
    return typeof obj === "function" && typeof obj.nodeType !== "number";
};

Deep copy derived function jQuery.isPlainObject

jQuery.isPlainObject(obj)
Test whether the object is a pure object (created by'{}'or'new Object').

jQuery.isPlainObject({}) // true
jQuery.isPlainObject("test") // false
var getProto = Object.getPrototypeOf;
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var fnToString = hasOwn.toString;
var ObjectFunctionString = fnToString.call( Object );

jQuery.extend( {
    isPlainObject: function( obj ) {
        var proto, Ctor;

        // Detect obvious negatives
        // Use toString instead of jQuery.type to catch host objects
        // obj is true or not [object Object object]
        // Return false directly
        if ( !obj || toString.call( obj ) !== "[object Object]" ) {
            return false;
        }

        proto = getProto( obj );

        // Objects with no prototype (e.g., `Object.create( null )`) are plain
        // The prototype does not exist such as Object.create(null) returning true directly.
        if ( !proto ) {
            return true;
        }

        // Objects with prototype are plain iff they were constructed by a global Object function
        Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
        // The constructor is a function, and fnToString. call (Ctor) ==== fnToString. call (Object);
        return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
    },
});

The extend ed function, which can also be deleted and written by itself, is a relatively core function in jQuery. Moreover, it has a wide range of uses, such as internal use or external use of extension plug-ins.

call chaining

jQuery is able to call chains because some functions return this after execution.
such as
AdClass, removeClass, toggleClass in jQuery source code.

jQuery.fn.extend({
    addClass: function(){
        // ...
        return this;
    },
    removeClass: function(){
        // ...
        return this;
    },
    toggleClass: function(){
        // ...
        return this;
    },
});

jQuery.noConflict Conflict Anti-Conflict Functions are available in many js Libraries

jQuery.noConflict API

Usage:

 <script>
    var $ = 'I'm the other one. $,jQuery Don't cover me up';
</script>
<script src="./jquery-3.4.1.js">
</script>
<script>
    $.noConflict();
    console.log($); // I'm the other $jQuery doesn't cover me.
</script>

jQuery.noConflict source code

var

    // Map over jQuery in case of overwrite
    _jQuery = window.jQuery,

    // Map over the $ in case of overwrite
    _$ = window.$;

jQuery.noConflict = function( deep ) {
    // If $=== jQuery already exists;
    // Assign existing $to window. $;
    if ( window.$ === jQuery ) {
        window.$ = _$;
    }

    // If deep is true and jQuery == jQuery already exists;
    // Assign existing _jQuery to window.jQuery;
    if ( deep && window.jQuery === jQuery ) {
        window.jQuery = _jQuery;
    }

    // Finally, return to jQuery
    return jQuery;
};

summary

This paper mainly analyses the whole structure of jQuery, including self-execution of anonymous functions, no new construction, support for a variety of specifications (such as commonjs, amd specifications), extension of core functions, chain invocation, jQuery.noConflict and so on.

Re-comb the source structure of the following learning.

// Source structure
( function( global, factory )
    "use strict";
    if ( typeof module === "object" && typeof module.exports === "object" ) {
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }

} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
    var    version = "3.4.1",

        // Define a local copy of jQuery
        jQuery = function( selector, context ) {
            return new jQuery.fn.init( selector, context );
        };

    jQuery.fn = jQuery.prototype = {
        jquery: version,
        constructor: jQuery,
        length: 0,
        // ...
    };

    jQuery.extend = jQuery.fn.extend = function() {};

    jQuery.extend( {
        // ...
        isPlainObject: function( obj ) {},
        // ...
    });

    init = jQuery.fn.init = function( selector, context, root ) {};

    init.prototype = jQuery.fn;

    if ( typeof define === "function" && define.amd ) {
        define( "jquery", [], function() {
            return jQuery;
        } );
    }
    jQuery.noConflict = function( deep ) {};

    if ( !noGlobal ) {
        window.jQuery = window.$ = jQuery;
    }

    return jQuery;
});

You can learn the ingenious design and architecture of jQuery and build your own js class library for your own use.
Related code and resource prevention github blog In this way, the readers who need it can take their own initiative.

The next article may be about the overall source architecture of underscorejs.

Readers are welcome to comment on any inadequacies or improvements they may find. In addition, I think it's a good writing, which can be praised, commented and forwarded. It's also a kind of support for the author.

The author's previous articles

Interviewer asked: JS inheritance

Interviewer asked: JS points to this

Interviewer asked: Can you simulate the call and apply method of JS?

Interviewer asked: Can we simulate the bind method of JS implementation?

Interviewer asked: Can you simulate the new operator that implements JS?

The front end uses the puppeteer crawler to generate the PDF of React.js Books and merge them.

Extended Reading

Chokcoco: jQuery-v1.10.2 Source Code Interpretation

chokcoco: [Deep and Simple jQuery] Source Code Analysis--Overall Architecture

Songjz: jQuery Source Series (1) Overall Architecture

about

Author: Often in the name of Ruochuan mixed in rivers and lakes. On the front road | PPT enthusiasts | know little, only good at learning.

Personal Blog

segmentfault Front-end Vision Column The front-end vision column has been opened, and you are welcome to pay attention to it.~

Nuggets column Welcome your attention~

Knowing Front-end Vision Column The front-end vision column has been opened, and you are welcome to pay attention to it.~

github blog The relevant source code and resources are all put here. Find a star.^^~

Posted by zoozoo on Mon, 29 Jul 2019 03:33:29 -0700