Home > Article > Web Front-end > A brief discussion on jQuery core architecture design
jQuery is no stranger to everyone, so I won’t say much here about what it is and what it does. The purpose of this article is to discuss the core of jQuery through a simple analysis of the source code. Architectural design, and how jQuery leverages advanced features in JavaScript to build such a great JavaScript library.
1 First introduction to jQuery
From the core function point of view, jQuery only does one simple and ordinary thing: query. Its syntax is so concise and clear that many people have already used jQuery without knowing what javascript is. To describe it in one word, it is: simplicity and simplicity. From a design perspective, we can divide the methods provided by jQuery into two major categories: static methods and instance methods. Static methods are methods accessed directly through $. These methods generally do not operate on DOM elements, but provide some common tools, such as ajax requests and some common operations on strings. In addition, jQuery also provides With its own extension mechanism, you can write the components you need through the extend method. The instance method is different from the static method. It is used to operate on the DOM elements queried by jQuery. jQuery executes $() to construct a jQuery object. This object stores all the DOM elements queried in an array method, and then in this The object's prototype chain implements methods for operating these DOMs. For example, the each() method is used to traverse each DOM element. You may notice that I just said that this object is stored "as an array", that is to say, the object constructed by jQuery is not an array, so what exactly is this object? In fact, this object is the core of jQuery, also known as "jQuery object". Therefore, the focus of this article is to analyze and discuss jQuery objects.
2 jQuery object
Generally, we will use jQuery like this:
$('div').each(function(index){ //this ...});
$('div') After execution, a jQuery object is returned. The each() method traverses the DOM elements in this object. Let's first look at the execution process of $('div') (the source code of this article is taken from jQuery 3.0):
jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); }
This method is the entry method of $('div'). $ is the abbreviation of jQuery, which is equivalent to jQuery('div'). It can be seen that this method only does one thing. That is to return the instance object of the jQuery.fn.init() function. So what is jQuery.fn.init? Let’s look at the following code:
init = jQuery.fn.init = = jQuery.fn;
jQuery.fn. init and init refer to the same method. This method queries the qualified DOM elements based on the selector and returns them. But you will find that what is returned is this. What is this? We will analyze it later. Let’s first look at the following sentence:
init.prototype = jQuery.fn;
What does this sentence mean? This sentence makes the prototype object of the init method point to the jQuery.fn object. , so what the hell is jQuery.fn? Let's continue to look at the code:
jQuery.fn = jQuery.prototype = { // The current version of jQuery being used jquery: version, constructor: jQuery, // The default length of a jQuery object is 0 length: 0, // Execute a callback for every element in the matched set. each: function( callback ) { return jQuery.each( this, callback ); }, splice: arr.splice };
In order to save space, I have omitted some of the code. As can be seen from here, jQuery.fn is actually the prototype object of jQuery. This prototype object is defined in There are some methods to operate on this object. If you feel a little confused at this point, don't worry, let's sort out the ideas: jQuery first defines an init method, and then defines a series of operation methods on the init prototype object prototype. Finally, the instance object of the init method is returned. So the above process can be simplified as follows (pseudocode representation):
var init = function(selector,context,root){ //... return this; } init.prototype = { length:0, each:function(callback){ //... }, splice:[].splice } jQuery = function(selector,context,root){ return new init(selector,context,root); }
Then the question is, why are the methods in jQuery.fn not directly defined on the init prototype, but defined On jQuery's prototype object?
Actually, the purpose of this is to improve the query efficiency of jQuery. If it is directly defined on the prototype object of init, then every time a query is executed, such a huge prototype object will be created in the memory, and If this object is defined on jQuery's prototype, this object will be initialized when jQuery is loaded and will always exist in memory. Every time $() is executed in the future, you only need to point the prototype in init to this object. Instead of creating the same object every time.
Let’s take a look at what exactly this is returned in the init function. I said in my previous blog that this in the function always points to the caller during runtime. So who is the caller of init? ? It seems that the caller cannot be found in the above code. At this time, we need to deeply understand the operating mechanism of the new operator. Borrowing from my previous description of the new operator in my blog, we will carry out the execution process of new init(). It’s broken down as follows:
new init(selector,context,root) = { var obj = {}; obj.__proto__ = init.prototype; init.call(obj,selector,context,root); return typeof result === 'obj'? result : obj; }
It can be seen from the above decomposition process that when JavaScript creates an instance object through new, it will first create a common object obj, and then point the internal attribute __proto__ of obj to the prototype object of init, so obj The prototype chain will be changed, and step 3 uses the call method to call init(), so this in init refers to the obj object here.
After init() is executed, will store all matched DOM objects in the form of an array into this object and return it, that is, the obj object will be returned, and the new operator will eventually This obj object is returned as the new instance object. So the instance object returned by the new operator has two characteristics: first, it contains the DOM query result set, and second, its prototype chain inherits the prototype of init, and the prototype of init points to the jQuery.fn object, so Instance objects also have these operation methods.
jQuery creates a jQuery object every time a query is executed, and in the same application, all jQuery objects share the same jQuery prototype object. Therefore, the jQuery object not only contains the DOM query result set, but also inherits the operation methods on the jQuery prototype object. In this way, you can directly call methods to manipulate these DOM elements after querying. This is the core architecture design of jQuery, which is simple, convenient and practical!
If you still don’t understand the above explanation, don’t worry, I wrote a complete small project jDate according to jQuery’s design ideas, you can compare and understand! The jDate project has been uploaded to GitHub. You can click here to view the complete code: jDate. If you have different opinions, please feel free to discuss!
3 Defects of jQuery
By analyzing the core architecture of jQuery, we will find that every time a query is executed, jQuery will build a complex jQuery in memory Object, although every jQuery object shares the same jQuery prototype, the query process of jQuery is far more complicated than you think. It must consider various matching identifiers and the compatibility of different browsers. Therefore, if you only do some simple operations on the DOM, it is recommended to use the native method querySelector instead of jQuery. However, when using the native method, you may have to do some compatibility work for different application scenarios. You must learn to make trade-offs and not overdo it. Depends on jQuery!