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:
- similar
Call (instance) []. Push.
, the use ofcall
Change the internalthis
Point to the - or
jQuery.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
- Equivalent to carrying out
new jQuery.fn.init(selector, context);
Is returnedinit
Method (class), assuming the instance isA
- Then: can be obtained
A.__proto__===init.prototype
- And you know that from the code
init.prototype
= >jQuery.fn
= >jQuery.prototype
And letinit
The prototype is redirected to the jQuery prototype, so it is finally executednew init
It’s almost executednew jQuery
- so
A.__proto__===jQuery.prototype
Summary: Based on JQ selector $(…) , jQuery (…). Get an instance of the jQuery class
- Purpose: To allow users to use the
jQuery
/$
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
init
The 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
-
if (! selector) return this; $(“”), $(null), $(undefined), $(false) returns an empty JQ object with public methods only on __proto__
-
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
-
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: use
JQ object [index]
或JQ object.get
Use 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
- To perform first
eq()
Supports negative indexes - Eq return
pushStack()
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 pushStack()
The concrete implementation is based onmerge
Method, concatenate the pseudo-array, and place the empty JQ object (length
0) combined with a DOM object of length 1 and returnedmerge
Note 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
, butconcat
Arrays only,merge
Method can support the processing of collections of class arrays- Pay attention to
pushStack
Also returned to theprevObject
Property, 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 $(‘
‘) 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
$.extend
Method usage
Extend user – defined methods tojQuery
/ jQuery.fn
上
-
Extend jQuery as an object to its private properties and methods (complete the library)
$.extend({ xxx: function () { console.log('xxx'); }}); $.xxx();Copy the code
-
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.
-
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,
-
$.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