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

Time:2019-11-21

Although it is rarely used nowjQueryButjQueryPopularMore than 10 yearsOfJS Library, it is still necessary to learn its source code. You can also learn to build your ownjsClass library, job interview can add a lot of color.

This article is aboutv3.4.1 Edition.
unpkg.comSource address: https://unpkg.com/jquery@3.4

jQuery githubWarehouse

self-executing anonymous function

(function(global, factory){

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

});

External variables and functions cannot be accessed, and external variables can be accessed inside, but if their own variables are defined inside, external variables will not be accessed.
Anonymous functions wrap the code inside to prevent conflicts with other codes and pollution of the global environment.
Readers who don’t know much about self executing functions can refer to this article.
[[translation] javascript: execute function expression now (Iife)] (https://segmentfault.com/a/11

In the browser environment, finally$andjQueryFunction mount towindowSo it can be accessed outside$andjQueryNow.

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

Support multiple environments such as commonjs and AMD specifications

Commonjs specification support

commonjsImplementation of key representativesnodejs

//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, factory (global, true) will be returned;
        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. If window exists, it returns window. If this does not exist, it returns 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;
    } );
}

The CMD specification mainly represents seajs

Unfortunately,jQueryIt’s not exposed in the source codeseajsSupport. But there are also programs online. I won’t mention it specifically here. After all, it’s not used nowseajsNow.

No new construction

In fact, it cannewBecausejQueryIt’s a function. And nonewThe effect is the same.
New shows the return object, so thejQueryThe effect of the function is the same.
If yesnewWhat does the operator do. Please refer to my previous article.

Interviewer: can we simulate the new operator of JS

Source code:

var
    version = "3.4.1",

    // Define a local copy of jQuery
    jQuery = function( selector, context ) {
        //Return the object after new
        return new jQuery.fn.init( selector, context );
    };
jQuery.fn = jQuery.prototype = {
    //JQuery current version
    jquery: version,
    //Fix 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

I drew a picture about thisjQueryArchetypal diagram is better than thousand words.
Learn the overall architecture of jQuery source code, and build your own JS class library

<sciprt>
</script>
console.log({jQuery});
//In the Google browser console, you can see that there are many static properties and methods under the jQuery function, and many properties and methods on jquery.fn.

VueSource code, also followjQuerySimilarly, the implementation isVue.prototype._initMethod.

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) {};
};

One of the core functions extend

Usage:

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

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

jQuery.extend API
jQuery.fn.extend API

Here are a few examples:
(the example can be put into the jquery.extend example codepen of online editing code, which can be run directly).

// 1. jQuery.extend( target)
var result1 = $.extend({
    Job: 'front end development engineer',
});

Console. Log (result1, 'result1', result1. Job); // $function adds a property job // front end development engineer

// 2. jQuery.extend( target, object1)
var result2 = $.extend({
    Name: 'ruokawa',
},
{
    Job: 'front end development engineer',
});

console.log(result2, 'result2'); // { Name: 'ruokawa', job: '前端开发工程师' }

//Deep copy
// 3. jQuery.extend( [deep ], target, object1 [, objectN ] )
var result3 = $.extend(true,  {
    Name: 'ruokawa',
    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:extendFunctions can be implemented tojQueryFunction can realize either shallow copy or deep copy. You can add static methods and properties to jQuery, as well asjQuery.fn(that is to say)jQuery.prototype)Add properties and methods on the, thanks tothisjQuery.extendInvocation timethisPointing tojQueryjQuery.fn.extendInvocation timethisPointing isjQuery.fn

Shallow copy implementation

Knowing this, it is relatively easy to implement shallow copy:

//Shallow copy implementation
jQuery.extend = function(){
    //Options are extended object1, object2
    var options,
    //Key on object object
    name,
    //Copy the value on the object, that is, the value to be copied
    copy,
    //Extension target object, may not be object, so or empty object
    target = arguments[0] || {},
    //Define I as 1
    i = 1,
    //Define the number of real parameters length
    length = arguments.length;
    //When there is only one parameter
    if(i === length){
        target = this;
        i--;
    }
    for(; i < length; i++){
        //It's not underfind and it's not null
        if((options = arguments[i]) !=  null){
            for(name in options){
                copy = options[name];
                //Prevent the dead cycle, continue to jump out of the current cycle
                if ( name === "__proto__" || target === copy ) {
                    continue;
                }
                if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }

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

The deep copy is mainly judged in the following code. It may be the value of array and object reference type. Make judgment.

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

In order to facilitate the debugging of readers, the code is also placed in jquery.extend shallow copy code to realize codepen, which can be run online.

Deep copy implementation

$.extend = function(){
    //Options are extended object1, object2
    var options,
    //Key on object object
    name,
    //Copy the value on the object, that is, the value to be copied
    copy,
    //Four new variables of deep copy: deep, SRC, copyisarray, clone
    deep = false,
    //Source target, which needs to be assigned to the above
    src,
    //The type of value to copy is a function
    copyIsArray,
    //
    clone,
    //Extension target object, may not be object, so or empty object
    target = arguments[0] || {},
    //Define I as 1
    i = 1,
    //Define the number of real parameters length
    length = arguments.length;

    //Handle deep copy
    if ( typeof target === "boolean" ) {
        deep = target;

        // Skip the boolean and the target
        //Target target object starts to move backward
        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 as 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++){
        //It's not underfind and it's not null
        if((options = arguments[i]) !=  null){
            for(name in options){
                copy = options[name];
                //Prevent the dead cycle, continue to jump 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 to be copied has a value, and it is a pure object
                //Or the value to be copied is an array
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
                    ( copyIsArray = Array.isArray( copy ) ) ) ) {

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

                    // Ensure proper type for the source value
                    //Copy the value of, and Src is not an array, the clone object is changed to an empty array.
                    if ( copyIsArray && !Array.isArray( src ) ) {
                        clone = [];
                        //The copied value is not an array, and the object is not a pure object.
                    } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
                        //Clone is assigned an empty object
                        clone = {};
                    } else {
                        //Otherwise clone = SRC
                        clone = src;
                    }
                    //Copyisarray needs to be reassigned to false for the next loop
                    copyIsArray = false;

                    // Never move original objects, clone them
                    //Call itself recursively
                    target[ name ] = jQuery.extend( deep, clone, copy );

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

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

In order to facilitate the debugging of readers, this code is also put in jquery.extend deep copy code to realize codepen, which can be run online.

Deep copy derived function isfunction

Judge 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
        //! object is true or not [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
        //Prototype does not exist. For example, object. Create (null) returns 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;
        //Constructor is a function, and fntostring. Call (ctor) = = fntostring. Call (object);
        return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
    },
});

extendFunction can also be deleted and written by yourself, which isjQueryOne of the core functions in. Moreover, it has a wide range of uses, which can be used internally or externally.

call chaining

jQueryChain call is possible because some functions are executed afterreturn this
such as
jQuerySource codeaddClassremoveClasstoggleClass

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

jQuery.noConflictQuite a lotjsConflict prevention functions in Libraries

jQuery.noConflict API

Usage:

<script>
    Var $= 'I am another $, jQuery do not overwrite me';
</script>
<script>
</script>
<script>
    $.noConflict();
    Console. Log ($); // I am another $, jQuery do not overwrite 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 the existing "$" to window. $;
    if ( window.$ === jQuery ) {
        window.$ = _$;
    }

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

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

summary

The full text mainly through the analysisjQueryOverall structure, self executing anonymous function, nonenewConstruct and support multiple specifications (such as commonjs and AMD specifications) and core functionsextend, chain calljQuery.noConflictAnd so on.

Review the source structure learned in the following.

//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;
});

Can learnjQuerySmart design and architecture, for their own use, to create their ownjsClass library.
Relevant code and resources are placed in GitHub blog, which can be taken by the readers themselves.

The next article is learningunderscorejsSource code architecture.
Learn the overall architecture of anderscorejs and build its own functional programming class library

Readers are welcome to comment on any defects or improvements. In addition, I think it’s well written. I can like it, comment and forward it. It’s also a kind of support for the author.

Previous articles of the author

Interviewer: Inheritance of JS
Interviewer: JS’s this point
Interviewer asked: can we simulate the call and apply methods of JS
Interviewer: can you simulate the bind method of JS
Interviewer: can we simulate the new operator of JS
The front end uses the puppeter crawler to generate the PDF of react.js small book and merge it

Extended reading

Chokcoco: jQuery – v1.10.2 source code interpretation
Chokcoco: source code analysis — overall architecture
Songjz: jQuery source series (I) overall architecture

about

Author: Chang YiIf ChuanHe wandered in the Jianghu for his name. On the front road, PPT enthusiasts know little, only good at learning.
Personal blog https://lxchuan12.github.io
GitHub blog, the relevant source code and resources are put here, find astar^_^~

Wechat communication group

Add in Wechatlxchuan12, note the source. Pull you into wechat Group [front view communication group].

Recommended Today

The method of obtaining the resolution of display by pyqt5

The code is as follows import sys from PyQt5.QtWidgets import QApplication, QWidget class Example(QWidget): def __init__(self): super().__init__() self.initUI() #Interface drawing to initui method def initUI(self): self.desktop = QApplication.desktop() #Get display resolution size self.screenRect = self.desktop.screenGeometry() self.height = self.screenRect.height() self.width = self.screenRect.width() print(self.height) print(self.width) #Show window self.show() if __name__ == ‘__main__’: #Create applications and objects app […]