JavaScript入门笔记[5]:jQuery的山寨源码

612 阅读4分钟

jQuery的灵感来源,一定是俄罗斯套娃吧

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)