
jQuery是目前最为长寿的JS函数库,还有日本up主小哥专门拍了一个视频。虽然从网上看到的,都是唱衰jQuery的声音,但是作为一只菜鸡,我还是从jQuery开始我的框架学习吧。
在我这只菜鸡看来,jQuery令我感到恐惧的地方,是它的链式操作,不就是套娃么。
链式操作的核心在于,jQuery返回的是一个jQuery对象,一个拥有jQuery全部功能的对象。
按照上述的说法,我大胆猜测,jQuery的本质是一个构造函数,虽然它不需要使用New
。那么,就试试看复现jQuery的源码吧~
window.jQuery = function(selector){
//要返回一个可操作的对象,所以jQuery是一个全局函数
const elements = document.querySelectorAll(selector);
//我们要创建一个jQuery返回的jQuery对象api
const api ={
//拿addClass功能作为例子,要注意addClass是api对象的功能
addclass(className){
for(let i=0; i<elements.length; i++){
elements[i].classList.add(className);
}
//return api; 运行结束之后返回一个可以调用操作的对象
//因为是返回调用该函数的对象,所以可以使用this
return this;
}
}
return api;
//因为调用jQuery函数的对象是window
//所以此处return this; 返回的是window对象
}
window.$ = window.jQuery;
//这样,我们的jQuery函数就可以拿$符直接操作了
既然我们找到了链式操作的返回对象,那么我们可以更近一步,在调用函数之后直接返回一个对象,我们的jQuery虽然不是一个构造函数,但是通过return,实现了构造对象的功能。
window.jQuery = function(selector){
//要返回一个可操作的对象,所以jQuery是一个全局函数
const elements = document.querySelectorAll(selector);
//我们可以直接返回一个对象
return{
addclass(className){
for(let i=0; i<elements.length; i++){
elements[i].classList.add(className);
}后返回一个可以调用操作的对象
//因为是返回调用该函数的对象,所以使用this
return this;
},
//我们新增一个find功能
find(selector,scope){
const arr = [];
for(let i=0;i<elements.length;i++){
//如果scope范围节点存在,则可以在范围节点的范围内搜索元素
arr = arr.concat(Arry.from(
(scope || document).querySelectorAll(selector)));
}
//问题来了,我们的find如果返回查找结果的话,岂不是丧失了链式操作
return arr;
}
}
//调用jQuery函数的对象是window
//如果此处return this; 返回的是window对象
}
window.$ = window.jQuery;
在我们简化了结构的jQuery函数中,添加了新的功能find
。
find(selector,scope){
const arr = [];
for(let i=0;i<elements.length;i++){
//如果scope范围节点存在,则可以在范围节点的范围内搜索元素
arr = arr.concat(Array.from(
(scope || document).querySelectorAll(selector)));
}
//问题来了,我们的find如果返回查找结果的话,岂不是丧失了链式操作
return arr;
}
经过find
查找后,我们会获得一个新的数组arr,arr包含了我们所需要的元素,但是直接返回arr,意味着链式操作会终止。
我们不能继续return this;
么?
如果继续return this;
,我们返回的是调用find
的对象。
我们要怎么继续链式操作呢?
让arr成为我们return的jQuery对象。
要怎么让让arr成为我们return的jQuery对象?
我们可以套娃,让jQuery再调用一次arr,让arr包装成jQuery对象
✿✿ヽ(°▽°)ノ✿
但是我们的jQuery只能接收字符串不是么
find(selector,scope){
const arr = [];
for(let i=0;i<elements.length;i++){
//如果scope范围节点存在,则可以在范围节点的范围内搜索元素
arr = arr.concat(Array.from(
(scope || document).querySelectorAll(selector)));
}
//问题来了,我们的find如果返回查找结果的话,岂不是丧失了链式操作
return jQuery(arr);
}
但是我们的jQuery只能接收字符串不是么
我们可以修改jQuery函数吖
window.jQuery = function(selectorOrArry){
let elements;
//const只支持在声明时为变量赋值,所以这里采用let
if(selectorOrArry === "string"){
//我们对输入进行判断,如果是字符串就当做选择器
elements= document.querySelectorAll(selectorOrArry);
}else if(selectorOrArry instanceof Array) {
//如果输入是数组,就进行套娃
elements= selectorOrArry;
}
return{
//这里就是我们所返回的jQuery元素
}
}
window.$ = window.jQuery;
至此,我们实现了jQuery的基本链式套娃操作。
强大的jQuery链式套娃操作就这么简单吗?不不不,我们的套娃怎会如此善罢甘休。jQuery真正厉害的地方,在于提供了end()
,可以返回上一次操作的jQuery对象。
既然end()
太骚了这么厉害,我们也要努力山寨一下。
window.jQuery = function(selectorOrArry){
let elements;
//const只支持在声明时为变量赋值,所以这里采用let
if(selectorOrArry === "string"){
//我们对输入进行判断,如果是字符串就当做选择器
elements= document.querySelectorAll(selectorOrArry);
}else if(selectorOrArry instanceof Array) {
//如果输入是数组,就进行套娃
elements= selectorOrArry;
}
return{
oldApi: selectorOrArry.oldApi,
//我们给返回的jQuery对象新增oldApi属性
end(){
//end只需要返回oldApi
return this.oldApi;
},
find(selector,scope){
const arr = [];
for(let i=0;i<elements.length;i++){
//如果scope范围节点存在,则可以在范围节点的范围内搜索元素
arr = arr.concat(
Array.from((scope || document).querySelectorAll(selector)));
}
arr.oldApi = this;
//我们将需要存档的jQuery对象作为属性增加到arr对象上
//虽然arr是数组,数组也是对象,也有属性
return jQuery(arr);
//这样,我们在套娃的时候,就会将oldApi一并返回
}
}
}
window.$ = window.jQuery;
截止目前,我们基本山寨完成了jQuery的套娃操作。但是如果每次操作都新增一个jQuery对象,也就意味着每个对象都会重复创建同样的对象属性,占据额外的空间。
如果引入对象的原型链,我们的jQuery就更像一个构造函数了就可以更高效了。
window.jQuery = function(selectorOrArry){
let elements;
if(selectorOrArry === "string"){
elements= document.querySelectorAll(selectorOrArry);
}else if(selectorOrArry instanceof Array) {
elements= selectorOrArry;
}
//我们新建一个返回的jQuery对象:api,该对象可以直接调用 jQuery.prototype
const api = Object.creat(jQuery.prototype);
//api需要有elements 和 oldApi属性
api.elements = elements;
api.oldApi = selectorOrArry.oldApi;
return api;
}
window.$ = window.jQuery;
//设置jQuery的原型对象
jQuery.prototype = {
constructor: jQuery,
end(){return this.oldApi;},
find(selector,scope){
const arr = [];
for(let i=0;i<this.elements.length;i++){
//find函数不能直接调用jQuery函数内的对象,只能调用调用find属性的对象的属性
arr = arr.concat(
Array.from((scope || document).querySelectorAll(selector)));}
arr.oldApi = this;
return jQuery(arr); }
}
至此,我们实现了jQuery的链式操作山寨源码。让我们来欣赏一下jQuery源码又是怎么处理的:
(function(root){
var jQuery = function{
//返回对象,jQuery实现对象的建构以及原型的继承
return new jQuery.prototype.init();
}
jQuery.prototype{
init(){ }
}
//原型共享
jQuery.prototype.init.prototype = jQuery.prototype;
root.$ = root.jQuery = jQuery;
})(this)