JQuery 2.0.3 source code analysis of core (I) overall architecture

  • 2020-03-30 03:06:46
  • OfStack

When you read an open source framework, what you want to learn most is the design ideas and implementation techniques.

Needless to say, jquery has written a lot of bad analysis over the years. I've read it a long time ago.

But these years is to do mobile terminal, has been used to zepto, recently took some time to give jquery to sweep again

I will not translate the source code, combined with their own practical experience together read it!

The latest on github is jquery - master, with the addition of the AMD specification, and I will follow the official update of 2.0.3

The overall architecture

The core of the jQuery framework is to match elements from HTML documents and perform operations on them,

Such as:


$().find().css()
$().hide().html('....').hide().

At least 2 problems can be found from the above writing

1. How to build jQuery objects

2. How to call jQuery methods

Analysis 1: no new build for jQuery

JavaScript is a functional language. Functions can implement classes. Classes are the most basic concept in object-oriented programming


var aQuery = function(selector, context) {
        //The constructor
}
aQuery.prototype = {
    // The prototype 
    name:function(){},
    age:function(){}
}
var a = new aQuery();
a.name();

This is the normal way to use it, and obviously jQuery doesn't work that way

JQuery does not use the new runtime to instantiate the jQuery display, but instead calls its functions directly

The way jQuery is written


$().ready() 
$().noConflict()

To do this, jQuery should be treated as a class, and $() should be an instance of the returned class

So change the code:


var aQuery = function(selector, context) {
       return new aQuery();
}
aQuery.prototype = {
    name:function(){},
    age:function(){}
}

New aQuery() returns an instance, but you can see the obvious problem: dead loop!

So how do you return the correct instance?

In javascript the instance this is only related to the prototype

So you can create an instance of the jQuery class as a factory method in the jquery.prototye prototype


var aQuery = function(selector, context) {
       return  aQuery.prototype.init();
}
aQuery.prototype = {
    init:function(){
        return this;
    }
    name:function(){},
    age:function(){}
}

When executing aQuery() returns an instance:

 

Obviously aQuery() returns an instance of the aQuery class, so this in init is also an instance of the aQuery class

So here's the problem: init's this points to the aQuery class, so if you use the init function as a constructor, what do you do with the internal this?


var aQuery = function(selector, context) {
       return  aQuery.prototype.init();
}
aQuery.prototype = {
    init: function() {
        this.age = 18
        return this;
    },
    name: function() {},
    age: 20
}
aQuery().age  //18

This is an error because this only points to the aQuery class, so you need to design a separate scope

The jQuery framework separates the scope handling


jQuery = function( selector, context ) {
        // The jQuery object is actually just the init constructor 'enhanced'
        return new jQuery.fn.init( selector, context, rootjQuery );
    },

It's obvious that by instantiating the init function, each time you build a new init instance object, you can separate this and avoid interaction confusion

So since they are not the same object then there must be a new problem

Such as:


var aQuery = function(selector, context) {
       return  new aQuery.prototype.init();
}
aQuery.prototype = {
    init: function() {
        this.age = 18
        return this;
    },
    name: function() {},
    age: 20
}
//Uncaught TypeError: Object [object Object] has no method 'name' 
console.log(aQuery().name())

Threw an error and couldn't find this method, so it's clear that the init of new is separate from this in the jquery class

How do I access the properties and methods on the jQuery class prototype?

        How about isolating the scope and using the scope of the jQuery prototype object, and accessing the jQuery prototype object in the return instance?

Key points of implementation


// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

Through prototype to solve the problem, the prototype of jQuery is passed to the jQuery. The prototype. The init. Prototype

In other words, jQuery's prototype object overrides the prototype object of the init constructor

Because it is reference passing, you don't need to worry about the performance of this circular reference


var aQuery = function(selector, context) {
       return  new aQuery.prototype.init();
}
aQuery.prototype = {
    init: function() {
        return this;
    },
    name: function() {
        return this.age
    },
    age: 20
}
aQuery.prototype.init.prototype = aQuery.prototype;
console.log(aQuery().name()) //20

Baidu borrows a picture of netizen, convenient and direct understanding:

Fn: fn is nothing special, just a reference to jQuery. Prototype

 

Analysis two: chain calls

DOM chain call processing:

1. Save JS code.

2. The same object is returned, which can improve the efficiency of the code

Chain-call across browsers by simply extending the prototype method and returning this.

Use the simple factory pattern under JS to specify all operations on the same DOM object to the same instance.

This principle is super simple


aQuery().init().name()
 decomposition 
a = aQuery();
a.init()
a.name()

If you break the code down, it's pretty clear that the basic requirement for chaining is that the instance of this exists, and it's the same


aQuery.prototype = {
    init: function() {
        return this;
    },
    name: function() {
        return this
    }
}

So we just need to chain-link access to this, because we return the current instance of this, so we can access our own prototype again


aQuery.init().name()

Advantages: save code, improve the efficiency of the code, the code looks more elegant

Worst of all is that all object methods return the object itself, meaning that there is no return value, which is not necessarily appropriate in any environment.

Javascript is a non-blocking language, so it's not not blocking, it's not blocking, so it needs to be event-driven, asynchronous to do some of the things that are supposed to block the process, so it's just synchronous chaining, asynchronous chaining jquery has introduced Promise since 1.5, jquery.deferred will be discussed later.

Analysis 3: plug-in interface

This is the main body of the jQuery framework, but according to the designer's habit, if want for jQuery or jQuery prototype method by adding attributes, also if you want to provide developers extension of the method, from the perspective of packaging should provide an interface to the literal can understand is extended to a function, rather than looking directly modify the prototype. Friendly user interface,

As you can see from the source code of jQuery, jquery.extend and jquery.fn.extend are actually different references to the same method


jQuery.extend = jQuery.fn.extend = function() {
jQuery.extend  right jQuery Its properties and methods are extended 
jQuery.fn.extend  right jQuery.fn Properties and methods of 

The extend() function provides a quick and easy way to extend functionality without breaking the prototype structure of jQuery

JQuery. Extend = function(){... }; This is an even function, which is 2 points to the same function, how can we do different things? This is the power of this!

Fn and jQuery are actually two different objects, which have been described previously:

      When called jQuery. Extend, this refers to a jQuery object (jQuery is a function and an object!). , so the extension here is on jQuery.
      When jQuery. Fn. Extend is called, this points to the fn object. JQuery. Fn and jQuery. Prototype point to the same object.
      What's added here is the prototype method, which is the object method. So the jQuery API provides the above two extension functions.

The realization of the extend


jQuery.extend = jQuery.fn.extend = function() {
    var src, copyIsArray, copy, name, options, clone,
        target = arguments[0] || {},    //JQuery. Extend (obj1, obj2), where the target is arguments[0]
        i = 1,
        length = arguments.length,
        deep = false;
    // Handle a deep copy situation
    if ( typeof target === "boolean" ) {    //If the first parameter is true, that is, jquery.extend (true, obj1, obj2); In the case
        deep = target;  //The target is true
        target = arguments[1] || {};    //Target to obj1
        // skip the boolean and the target
        i = 2;
    }
    // Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {  //Handle strange situations like jQuery. Extend ('hello', {Nick: 'casper})~~
        target = {};
    }
    // extend jQuery itself if only one argument is passed
    if ( length === i ) {   //Deal with this situation jquery.extend (obj), or jquery.fn.extend (obj)
        target = this;  //JQuery. Extend, this refers to jQuery; When jQuery. Fn. Extend, this refers to jQuery. Fn
        --i;
    }
    for ( ; i < length; i++ ) {
        // Only deal with non-null/undefined values
        if ( (options = arguments[ i ]) != null ) { //For example, jQuery. Extend (obj1, obj2, obj3, ojb4), options are obj2, obj3...
            // Extend the base object
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];
                // Prevent never-ending loop
                if ( target === copy ) {    //Prevent self - reference, not redundant
                    continue;
                }
                // Recurse if we're merging plain objects or arrays
                //If it is a deep copy, the copied property value itself is an object
                if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    if ( copyIsArray ) {    //The copied property value is an array
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];
                    } else {     The copied property value is one plainObject , such as { nick: 'casper' }
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }
                    // Never move original objects, clone them
                    target[ name ] = jQuery.extend( deep, clone, copy );  //Recursive ~
                // Don't bring in undefined values
                } else if ( copy !== undefined ) {  //Shallow copy, and the property value is not undefined
                    target[ name ] = copy;
                }
            }
        }
    }
    // Return the modified object
    return target;

Conclusion:

      Method to build a new object with new jQuery. Fn.init (), with the prototype object of the init constructor
      By changing the prorotype pointer, this new object also points to the prototype of the jQuery class
      So the constructed object continues with all the methods defined by the jquery.fn stereotype


Related articles: