The previous articles documented export issues and the encapsulation of data type judgments at one point.

This is a brief analysis of jQuery’s core architecture to learn about object-oriented and plug-in encapsulation.

Use methods from other prototypes

We want to use methods on other class prototypes, such as array push, in two ways:

  • similarCall (instance) []. Push., the use ofcallChange the internalthisPoint to the
  • orjQuery.prototype.push = arr.push

JQuery does this by taking the corresponding methods on the array prototype and putting them on your own prototype so that you can use the common methods provided by other prototypes.

In this way, the instance can use the methods of other classes when it finds the common attributes and methods provided by the prototype of its class through the prototype chain. We call this borrowing method duck type, as follows:


(function () {
    "use strict";
    var arr = [];
    var slice = arr.slice;// Get the corresponding method on the array prototype
    var push = arr.push;
    var indexOf = arr.indexOf;

    / / = = = = = = =
    var version = "3.5.1 track of",
        jQuery = function jQuery(selector, context) {
            return new jQuery.fn.init(selector, context);
        };
    jQuery.fn = jQuery.prototype = {
        constructor: jQuery,// Redirect the prototype to prevent no constructor
        jquery: version,
        length: 0.// Duck type put these methods on the array prototype on the jQuery prototype so that you can use the methods provided on other prototypes,
        // Similar to [].slice.call(instance), or jquery.push = arr.push, can also be used
        push: push,
        sort: arr.sort,
        splice: arr.splice,
       
    window.jQuery = window.$ = jQuery; }) ();Copy the code

object-oriented

$(‘.box); $(‘.box); $(‘.box); So let’s analyze it

The code is as follows:

(function () {
    "use strict";
    var version = "3.5.1 track of",
        jQuery = function jQuery(selector, context) {
            return new jQuery.fn.init(selector, context);
        };
    jQuery.fn = jQuery.prototype = {
        constructor: jQuery,
        jquery: version,
        length: 0};var init = jQuery.fn.init = function init(selector, context, root) {
        / /...
        };
    init.prototype = jQuery.fn;
    / /...
    window.jQuery = window.$ = jQuery; }) ();Copy the code

Execute the $([selector]) method as follows

  1. Equivalent to carrying outnew jQuery.fn.init(selector, context);Is returnedinitMethod (class), assuming the instance isA
  2. Then: can be obtainedA.__proto__===init.prototype
  3. And you know that from the codeinit.prototype= >jQuery.fn= >jQuery.prototypeAnd letinitThe prototype is redirected to the jQuery prototype, so it is finally executednew initIt’s almost executednew jQuery
  4. soA.__proto__===jQuery.prototype

Summary: Based on JQ selector $(…) , jQuery (…). Get an instance of the jQuery class

  • Purpose: To allow users to use thejQuery / $Execute as a normal function, but the end result is to create an instance of its class that is easy for users to use
  • The idea of this processing pattern is actually the factory design pattern

An interview question: Is it possible to create an instance of the current function without using the new operator? The core principle is to use jquery.fn to do a prototype transfer

Add another instance of a constructor that does not execute a function based on new:

function* fn() {}
fn.prototype.x = 100;
let f = fn();
// f.__proto__===fn. Prototype f is also an instance of fn.
Copy the code

initThe logic in the constructor

So init takes three arguments, and selector can take different kinds of arguments, and we’re going to sort them out

Jquery.fn. init is passed selector, context, which corresponds to how jQuery is used

  • $('.box')Find owned throughout the document'box'The style class
  • $('#commHead .box')In the ID for'commHead'To find the box style class (descendant lookup)
  • $('.box',document.getElementById('commHead'))Same meaning as above

If you pass in a DOM

(function () {
    "use strict";
    / /...

    var version = "3.5.1 track of",
        jQuery = function jQuery(selector, context) {
            return new jQuery.fn.init(selector, context);
        };
    jQuery.fn = jQuery.prototype = {
       / /...
        length: 0};var rootjQuery = jQuery(document),
        init = jQuery.fn.init = function init(selector, context, root) {
            var match, elem;
            / / processing: $(" "), $(null), $(undefined), $(false) returns an empty JQ object
            if(! selector)return this;
            root = root || rootjQuery;
            if (typeof selector === "string") {
               / /...
            } else if (selector.nodeType) {
                // Pass a native DOM/JS object: convert the DOM object to a JQ object "so you can use the methods provided on the JQ prototype"
                this[0] = selector;
                this.length = 1;
                return this;
            } else if (isFunction(selector)) {
             / /...}}; init.prototype = jQuery.fn;window.jQuery = window.$ = jQuery; }) ();Copy the code
  1. if (! selector) return this; $(“”), $(null), $(undefined), $(false) returns an empty JQ object with public methods only on __proto__

  2. Typeof Selector === “string” is a way of dealing with passing in a string selector, so it’s a little bit more complicated logic here, so I’m going to skip that

  3. NodeType handle: When a DOM object is passed in, place the DOM object on property [0], which is equivalent to wrapping a layer over the DOM object, and return a pseudo-array of jQ objects, each numeric attribute being a DOM object.

JQ object: JQ instance object “is the result obtained based on the selector”, usually returns a collection of class arrays with attributes such as index and length, “DOM/JS object” : Element objects obtained based on browser-built methods “They are related instances of browser-built classes”

  • “DOM object” to “JQ object” :$(" DOM object ")
  • JQ Object get DOM object: useJQ object [index]JQ object.getUse methods on the built-in class stereotype

On prototype methodget VS eq

Get gets the DOM object, eq returns the jQuery object

Get gets the DOM value of a JQ object from an array (slice.call(this)), and then the DOM value of a JQ object from an array (slice.call(this))

Eq also looks for an item in the JQ object set based on the index, but instead of returning a “DOM object”, a new “JQ object” is returned

  1. To perform firsteq()Supports negative indexes
  2. Eq returnpushStack()The result of this execution is an empty JQ object, and theeq()This function returns a new JQ object containing only the DOM value indexed by eq
  3. pushStack()The concrete implementation is based onmergeMethod, concatenate the pseudo-array, and place the empty JQ object (length0) combined with a DOM object of length 1 and returned
  4. mergeNote that he passes two sets, placing every item in the second set at the end of the first set, and combining the two sets returns the first set. Array-likeconcat, butconcatArrays only,mergeMethod can support the processing of collections of class arrays
  5. Pay attention topushStackAlso returned to theprevObjectProperty, as can be seen in the code, this is after using EQ, the original root JQ object, in the chain call, can quickly go back to the original operation of the JQ object. For example,$('body').prev().addClass('xxx').prevObject.addClass('clearfix')
(function () {
    "use strict";
    var arr = [];
    var slice = arr.slice;// Get the corresponding method on the array prototype
    var push = arr.push;

    / / = = = = = = =
    var version = "3.5.1 track of",
        jQuery = function jQuery(selector, context) {
            return new jQuery.fn.init(selector, context);
        };
    jQuery.fn = jQuery.prototype = {
        constructor: jQuery,
        jquery: version,
        length: 0.push: push,
        // Convert "JQ object" to "DOM object" based on index
        get: function (num) {
            // Convert "JQ object" class array collection to array collection
            if (num == null) return slice.call(this);
            // Support negative numbers as indexes
            return num < 0 ? this[num + this.length] : this[num];
        },
        // Return a new JQ object instead of a DOM object.
        eq: function (i) {
            // Support negative numbers as indexes
            var len = this.length,
                j = +i + (i < 0 ? len : 0);
            return this.pushStack(j >= 0 && j < len ? [this[j]] : []);
        },
        pushStack: function (elems) {
            // this. Constructor ->jQuery => jQuery() => empty JQ object
            var ret = jQuery.merge(this.constructor(), elems);
            // prevObject: In chained calls, you can quickly return to the JQ object of the original operation (root JQ object)
            ret.prevObject = this;
            returnret; }};// Pass two sets, place each item in the second set at the end of the first set, "merge two sets", return the first set
    // + is similar to concat for arrays, but only for arrays
    The merge method supports processing of arrays of classes
    jQuery.merge = function merge(first, second) {
        var len = +second.length,
            j = 0,
            i = first.length;
        for (; j < len; j++) {
            first[i++] = second[j];
        }
        first.length = i;
        return first;
    };

    / / = = = = = = =
      var  init = jQuery.fn.init = function init(selector, context, root) {
            var match, elem;
            / / processing: $(" "), $(null), $(undefined), $(false) returns an empty JQ object
            if(! selector)return this;
            if (typeof selector === "string") {/... }else if (selector.nodeType) {
                // Pass a native DOM/JS object: convert the DOM object to a JQ object "so you can use the methods provided on the JQ prototype"
                this[0] = selector;
                this.length = 1;
                return this;
            } else if (isFunction(selector)) {
              / /...
            }
            / /...
        };
    init.prototype = jQuery.fn;
    window.jQuery = window.$ = jQuery; }) ();Copy the code

If you pass in a function

If you pass in the function, that is, $(function (){}), you can see this in the following code

var rootjQuery = jQuery(document) => root = root || rootjQuery; Go down to the return value

returnroot.ready ! = =undefined ?
                    root.ready(selector) :
                    selector(jQuery);
Copy the code

$(function (){}) = function (){}

ReadyList returns a promise and resolves before executing the passed function

What does the readyList do? Since this part of the code is too scattered and jumping, I won’t paste it. Here’s how it works:

ReadyList, listen for the ‘DOMContentLoaded’ event, and resolve promise when this event is triggered.

‘DOMContentLoaded’ is an event that is triggered when the DOM structure in the page is completely loaded, and then the callback function is executed

AddEventListener (‘load’,function(){}) Load event refers to waiting for all resources on the page to complete loading, including DOM structure loading and other resources loading.

$(function (){}) $(document).ready(function (){})

(function () {
    "use strict";
    var version = "3.5.1 track of",
        jQuery = function jQuery(selector, context) {
            return new jQuery.fn.init(selector, context);
        };
    jQuery.fn = jQuery.prototype = {};
    var rootjQuery = jQuery(document),
        init = jQuery.fn.init = function init(selector, context, root) {
            var match, elem;
            if(! selector)return this;
            root = root || rootjQuery;
            if (typeof selector === "string") {
              / /...
            } else if (selector.nodeType) {
              / /...
            } else if (isFunction(selector)) {
                returnroot.ready ! = =undefined ?
                    root.ready(selector) :
                    selector(jQuery);
            }
            / /...
        };
    init.prototype = jQuery.fn;
    / / = = = = = = =
    var readyList = jQuery.Deferred();
    jQuery.fn.ready = function (fn) {
        readyList
            .then(fn)
            .catch(function (error) {
                jQuery.readyException(error);
            });
        return this;
    };
    window.jQuery = window.$ = jQuery; }) ();Copy the code

If you pass in a string

$(‘.box’) selector type and $(‘

XXX

‘) HTML string type

If you pass in something else

For example, passing in an array

We can see that jQ changes each item of the array to a pseudo-array

(function () {
    "use strict";
    var push = arr.push;
    var version = "3.5.1 track of",
        jQuery = function jQuery(selector, context) { return newjQuery.fn.init(selector, context); }; jQuery.fn = jQuery.prototype = {jquery: version,
        length: 0.push: push
    };
    jQuery.merge = function merge(first, second) {
        var len = +second.length,
            j = 0,
            i = first.length;
        for (; j < len; j++) {
            first[i++] = second[j];
        }
        first.length = i;
        return first;
    };
    var init = jQuery.fn.init = function init(selector, context, root) {
            var match, elem;
            if(! selector)return this;
            root = root || rootjQuery;
            if (typeof selector === "string") {... }else if(selector.nodeType) {... }else if(isFunction(selector)) {... }return jQuery.makeArray(selector, this);
        };
    init.prototype = jQuery.fn;
    jQuery.makeArray = function makeArray(arr, results) {
        var ret = results || [];
        if(arr ! =null) {
            if (isArrayLike(Object(arr))) {
                jQuery.merge(ret,
                    typeof arr === "string" ? [arr] : arr
                );
            } else{ push.call(ret, arr); }}return ret;
    };
    window.jQuery = window.$ = jQuery; }) ();Copy the code

Return jquery. makeArray(selector, this); If an array or jquery object is passed in, isArrayLike checks it, and merge the empty jQ object with the array. If it’s any other type of value, we call push, place it first in the pseudo-array of jQ objects, and then return the same jQuery object

Handwriting stronger each method

The each method in jquery can iterate over an array/class array/object.

Requirement: Support callback function return value processing: the passed callback returns false to end the loop. This is something that the built-in method forEach/Map does not have

Add logic to pass in parameter detection and logic to end the loop

// Loop through array/array/object "supports callback function return value processing: return false to end loop, which the built-in forEach/map method does not have"
    var each = function each(obj, callback) {
        //' function. prototype' returns an anonymous empty Function that does nothing
        typeofcallback ! = ="function" ? callback = Function.prototype : null;
        var length,
            i = 0,
            keys = [];
        if (isArrayLike(obj)) {
            // Array or class array
            length = obj.length;
            for (; i < length; i++) {
                var item = obj[i],
                // Make this refer to the element itself,(same as forEach)
                    result = callback.call(item, item, i);
                    // The callback returns false, in conjunction with the prime loop
                if (result === false) break}}else {
            / / object
            // To avoid the for in loop problem, we use keys+ loop to iterate over the object
            keys = Object.keys(obj);
            typeof Symbol! = ="undefined" ? keys = keys.concat(Object.getOwnPropertySymbols(obj)) : null;// Contains the Symbol attribute
            i = 0;
            length = keys.length;
            for (; i < length; i++) {
                var key = keys[i],
                    value = obj[key];
                    // This is done and the result is returned
                if (callback.call(value, value, key) === false) break; }}return obj;
    };
Copy the code

$.extend/$.fn.extend

Depth merge about objects

Shallow merge of objects: Merge only the contents of the first level of an object

Deep merge of objects: If the first level of content is also an object later, that continues to merge the next level of objects, rather than complete attribute overwriting

Shallow merger

Object.assign()

Object.assign() allows shallow merging of objects

let obj1 = {
    url: '/api/list'.method: 'GET'.headers: {
        'Content-Type': 'application/json'
    },
    cache: false
};

let obj2 = {
    params: {
        lx: 0
    },
    method: 'POST'.headers: {
        'X-Token': 'AFED4567FG'}};console.log(Object.assign(obj1, obj2));
Copy the code

Object.assign() Shallow merge: Merge obj2 into obj1, replace obj1 with the contents of obj2, and return obj1 (obj1 will change).

If you want to return a new object, you can do this with obj1 and obj2 unchanged:

console.log(Object.assign({}, obj1, obj2));
Copy the code

Deep merge

You can see that the headers property is overwritten directly, so how do we do a deep merge of objects? Take a look at JjQuery’s extend method, which allows for deep merging of objects.

Let’s start with the extend method, which can be used in several different ways

$.extendMethod usage

Extend user – defined methods tojQuery / jQuery.fn
  1. Extend jQuery as an object to its private properties and methods (complete the library)

         $.extend({
                xxx: function () {
                    console.log('xxx'); }}); $.xxx();Copy the code
  2. Extend properties and methods to its prototype, for example “results obtained based on JQ selector” call, write JQ plug-in use method

    $.fn.extend({
        xxx() {
            console.log('xxx'); }}); $('.box').xxx(); 
    Copy the code

Note: when there is only one object, $.extend(true,{… }) this extension extends the object to jquery, regardless of whether the first attribute is passed true or not

Implement the merge of two or more objects

Including shallow comparison for shallow merge, deep comparison for deep merge.

  1. Shallow merge: Similar to object. assign, $.extend(obj1, obj2), shallow merge two objects into the first Object, note that the first Object will change and return the first Object,

  2. $.extend(true, {}, obj1, obj2), the first argument must be true, followed by more than two objects. That’s deep merge, where you merge objects layer by layer

Source code analysis

The source code is parsed line by line

jQuery.extend = jQuery.fn.extend = function () {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[0) | | {},// If the first value is not passed or false is passed, make target empty
        i = 1,
        length = arguments.length,// How many values are stored
        deep = false;
    // If the first value is a Boolean, we want a deep merge.
    if (typeof target === "boolean") {
        deep = target;//deep assigns true
        target = arguments[i] || {};//target becomes the first object passed in
        i++;/ / I for 2
        // deep-> Boolean if you want deep merge. If I go here, it must be true
        // target-> the first object passed (if there is no deep, it is the first argument, if there is deep, it is the second argument)
        // the index of the second object passed in May not exist.
    }
    // Make sure that target is an object
    if (typeoftarget ! = ="object" && !isFunction(target)) target = {};
    // If the second object is not passed: just want to merge the contents of the first pass object into $/$.fn, corresponding to the first use method
    if (i === length) {$/$.fn = $/$.fn = $/$.fn = $/$.fn
        target = this; //-> target=$/$.fn
        i--; //-> the I index corresponds to the object passed
    }

    // That's all the logic does
    // 1. Change target to the first object passed, or $/$.fn
    // 2. I becomes the index of the first object to start merging, used to start merging sequentially with target

    // Target represents the object to be replaced, and target is returned.
    // The next loop takes the remaining objects "which may be one or more" and replaces them with target in turn
    for (; i < length; i++) {
        // options: Replace target's object with one of the remaining objects in each cycle
        options = arguments[i];
        if (options == null) continue;
        for (name in options) {
            // Copy gets each item in the object (we'll replace these items with the same item in target)
            copy = options[name];
            copyIsArray = Array.isArray(copy);
            // Prevent object nesting, which will form dead recursion (prevent circular references), i.e. refer to their own layer above
            if (target === copy) continue;
            if (deep && copy && (jQuery.isPlainObject(copy) || copyIsArray)) {
                // Deep merge The item in options must be a pure object or array, so we need to compare it with the corresponding item in target to achieve deep merge. Otherwise, we can replace the item in Target with the item in Options
                // Options copy object/array
                // SRC represents the target item "clone".
                src = target[name];
                // The type of the object passed in is the same as that of the replaced object
                if (copyIsArray && !Array.isArray(src)) {
                    // If copy is an array but SRC is not, clone is an array
                    clone = [];
                } else if(! copyIsArray && ! jQuery.isPlainObject(src)) {// Copy can only be arrays or pure objects
                    // If copy is a pure object, but SRC is not, make clone equal to the empty object
                    clone = {};
                } else {
                    clone = src;
                }
                copyIsArray = false;
                // Implement deep comparison and deep merge of current items based on recursion
                target[name] = jQuery.extend(deep, clone, copy);
            } else if(copy ! = =undefined) {
                / / shallow consolidationtarget[name] = copy; }}}return target;
};
Copy the code

Note that circular references require special handling