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