JQuery 源码简单刨析,分析源码不再痛苦~

文章目录

  • Jquery 源码浅谈
    • 开题吐槽
    • Hello Jquery
    • 链式操作基础
    • 封装
      • eq 封装
      • end 封装
      • on 封装
      • css 封装
      • css 扩展:$.cssNumber
      • css 扩展:$.cssHooks
    • 复盘总结
    • 碎碎念
    • 代码

Jquery 源码浅谈

开题吐槽

有人上来就说了:啊啊这都 0202 年了,怎么还学 JQuery 呢,远古时代么?

其实写这篇文章的目的呢,主要就是目前在复习 面向对象 的知识,通过分析 JQuery 源码的思想,可以帮助我更好的理解面向对象~

而且我原先还真没看过JQ的源码,借此跟大家分享一下,也请大家批评指正!

教程是基于开课吧的课程,自己整理出的笔记!!!


Hello Jquery

首先看一个例子:我想给 div 元素添加点击事件,那么我们可以用 JQ 这样写

$('div').click(function () {
    console.log('点击了box1')
})

我们有没有思考过,Jquery 怎样帮助我们实现这个功能的呢?

我们先来分析一下:$('.box1') 是一个 Jquery 对象,然后调用对象中的 click 方法,方法里传递了一个匿名函数!

知道了这些,就可以开始写一个自己的 jq 了来,vscode上号!

首先新建一个自己的 myJquery.js,然后 cv 大法。

// myJquery.js
function $(arg) {
	// $() 函数执行后,返回一个对象
	// 对象中有 click 方法
    return {
        click(fn) {
        	fn()
            // 这里应该写点击事件的逻辑
            // 但是我并没有添加监听事件
        },
    };
}

当然这个是不完整的,因为我还没有绑定事件呢,别着急,咱们一点一点来~

首先我要做一个简单的优化,因为这样写感觉有点乱,写成类的话看起来更清爽些(写成类的话注意兼容性的问题,我这里没有考虑兼容性)

// myJquery.js
class myJquery{
    constructor(){}
    click(){
        console.log("点击事件") 
    }
}
function $(ele){
	// 这里返回一个实例化对象,本质还是一个对象~
	// 之后我们把焦点放在处理构造函数即可
    return new myJquery(ele)
}

然后咱们完善一下点击事件的逻辑:

  • 首先要获取到元素
  • 然后添加绑定事件
// myJquery.js
class myJquery{
    constructor(ele){
        // 通过元素
        this.ele = document.querySelectorAll(ele);
    }
    click(){
        // 添加绑定事件
        this.ele.forEach(item=>{
            item.addEventListener('click',function(){
                console.log(this)
            })
        })
    }
}

JQuery 源码简单刨析,分析源码不再痛苦~_第1张图片
这样虽然能够实现功能,但是 Jquery 本身并不是这样搞的

我们可以打印一下 Jquery 对象本身,然后再打印一下我们写的 myJquery 对象本身,对比一下

console.log($("div"));

JQuery 源码简单刨析,分析源码不再痛苦~_第2张图片
在这里插入图片描述

我们看到,Jquery 把每个元素挂到了对象上,像一个伪数组对吧~,并且有 lengthprevObject 属性

看到学霸的答案了,咱们边抄边想为啥这么设计~

// myJquery.js
class myJquery {
    constructor(ele) {
        let arr = document.querySelectorAll(ele);
        // 把每个节点挂到 this 上
        arr.forEach((item, index) => {
            this[index] = item;
        });
        this.length = arr.length;
    }
}
function $(ele) {
    return new myJquery(ele);
}

写完之后,打印一下咱们的节点:

JQuery 源码简单刨析,分析源码不再痛苦~_第3张图片

看,是不是比较像jQuery对象了,不过还少一个属性 prevObject,待会儿会讲到的(在 end 封装 中)

接着把添加节点的那一坨代码提取到 addElement 中去,(为了使代码更清晰),然后再把点击事件的逻辑补充一下~

// myJquery.js
class myJquery {
    constructor(ele) {
        let arr = document.querySelectorAll(ele);
        this.addElement(arr);
    }
    addElement(arr) {
        // 把每个节点挂到this上
        // 这里的 this 就是实例化的对象,就是下面 $ 函数返回的实例化对象
        arr.forEach((item, index) => {
            this[index] = item;
        });
        this.length = arr.length;
    }
    click(fn) {
        // 绑定事件
        for (let i = 0; i < this.length; i++) {
            this[i].addEventListener("click", fn, false);
        }
    }
}
function $(ele) {
    return new myJquery(ele);
}

JQuery 源码简单刨析,分析源码不再痛苦~_第4张图片
看!JQuery 简单的源码咱就搞定了!

至于为什么这么设计呢,我个人理解的哈,有以下的好处

  • 方便查找:我们把找到的节点挂到实例化的对象上,以后想查一下这个 Jquery对象选了谁,直接 console.log($("节点")) 即可,而你不挂到this上,this上就是空空的,没有东西的。
  • 方便源码的开发:比方说eqon,这类方法,我们开发源码的时候,如果this上有对象的话,直接for 循环遍历添加功能即可。

当然这只是我的愚见,多年以后回过头来再看看估计是另外一种感悟吼吼吼。

鸡汤总结:研究某一个框架,先去研究它 宏观如何实现,理解它的实现思想,再去研究如何实现的


链式操作基础

jq 是有链式调用的,就是跟链条一样点啊点的,举个例子

// 选择所有 div 中的下标为 0 的,然后绑定点击事件
$("div").eq(0).click(function(){
    console.log(2222);
})

那我们怎样实现呢?

首先看一下对象如何链式调用:

let obj = {
    fn1() {
        console.log("执行了 fn1")
        return this;
    },
    fn2() {
        console.log("执行了 fn2")
        return this;
    }
}
obj.fn1().fn2().fn1();
// console.log("执行了 fn1")
// console.log("执行了 fn2")
// console.log("执行了 fn1")

观察代码,主要还是把 this 给返还了,this 是实例化对象,也就是 obj

接下来的几个封装方法会用到它的!


封装

eq 封装

功能描述:eq(index) 拿到集合中的某一个

// myJquery.js
eq(index) {
    return new myJquery(this[index]);	// 返还对象
}

end 封装

jQuery 中还有个 end 方法,作用是返回上一个操作节点
比方说我 $("div").eq(1) 之后还想拿到前面的$("div"),就可以这样操作:$("div").eq(1).end()

主要的思想就是:当前 jquery对象有个 prevObject 属性,用来保存上一个操作的节点,所以end 操作其实没干什么,就是把 prevObject 返还给你。

// myJquery.js
end() {
    return this['prevObject'];
}

end 的这个操作,需要在初始化的时候处理一下,默认的前一个对象是 document

// myJquery.js
constructor(arg, root) {
    if (typeof root === 'undefined') {
        this['prevObject'] = [document];
    } else {
        this['prevObject'] = root;
    }
}

on 封装

jQuery 中的 on 方法,可以写很多个事件,比方说

// html
$(".box1").on("   mouseover   mousedown  ",function(){
    console.log("鼠标事件");
})

有个小优化,这里的字符串里可能有很多空格,就像上面的栗子那样

这里处理一下字符串,然后遍历绑定即可。

// myJquery.js
on(eventName, fn) {
   let reg = /\s+/g;
    // 去掉开头和结尾的空格
    eventName = eventName.trimStart().trimEnd().replace(reg, " ");
    let arr = eventName.split(" ");
    console.log(arr);       // (2) ["mouseover", "mousedown"]
    for (let i = 0; i < this.length; i++) {
        for (let j = 0; j < arr.length; j++) {
            this[i].addEventListener(arr[j], fn, false);
        }
    }
}

css 封装

jq 中调用 css 有如下几种方式

  • 获取属性:“$("div").css("background")
  • 设置属性1:$("div").css("width",200);
  • 设置属性2:$("div").css({"width":"200px","height":"200px",'opacity':0.5 });

可以看出他们的区别:

  • 获取属性:只需要传递一个字符串
  • 设置属性
    • 设置属性1:传递两个参数
    • 设置属性2:传递一个对象

我们可以根据区别,然后做不同的处理

css(...arg) {
    // 对于形参个数不固定,可以用 rest 参数来处理
    if (arg.length === 1) {
        if (typeof arg[0] === "string") {
            // 方式一:获取属性
            // ie6 的话可以用 currentStyle 解决兼容性问题
            return window.getComputedStyle(this[0], null)[arg[0]];
        } else {
            // 方式三:设置属性
            for (let i = 0; i < this.length; i++) {
                for (let j in arg[0]) {
                    this.setStyle(this[i], j, arg[0][j]);
                }
            }
        }
    } else {
        // 方式二:设置属性
        for (let i = 0; i < this.length; i++) {
            this.setStyle(this[i], arg[0], arg[1]);
        }
    }
}
setStyle(node, attr, val) {
    node.style[attr] = val;
}

写好之后可以在 html 里调用一下

$("div").css({"width":"200px","height":"200px",'opacity':0.5});

JQuery 源码简单刨析,分析源码不再痛苦~_第5张图片
嗯,看起来没啥大问题~


css 扩展:$.cssNumber

其实在 jquery 设置属性的时候,有些属性值你不写 px ,它也会帮你处理,比方说

// 200 并没有写单位,jquery 会帮你加上 px 的
$("div").css("width",200)

所以,我们的代码还可以优化一下,在设置样式属性的时候,判断一下是否是数字

setStyle(ele,styleName,styleValue){
    if(typeof styleValue === 'number'){
        styleValue = styleValue + "px";
    }
}

这样处理了,到底好不好呢?

其实并不好,因为像 opacity 这样的属性,他就 没有单位

那么jquery是如何处理的呢?

这里设置了静态属性 $.cssNumber :用来存放一些不需要加单位的样式属性

$.cssNumber = {
    animationIterationCount: true,
    columnCount: true,
    fillOpacity: true,
    flexGrow: true,
    flexShrink: true,
    fontWeight: true,
    gridArea: true,
    gridColumn: true,
    gridColumnEnd: true,
    gridColumnStart: true,
    gridRow: true,
    gridRowEnd: true,
    gridRowStart: true,
    lineHeight: true,
    opacity: true,
    order: true,
    orphans: true,
    widows: true,
    zIndex: true,
    zoom: true,
};

如果某一个属性不需要单位,我们可以这样判断一下

setStyle(node, attr, val) {
	// 这个属性不在 不需要加单位 的阵列里
    if(typeof val === 'number' && !(attr in $.cssNumber)){
        val = val + 'px';
    }
    node.style[attr] = val;
}

然后在测试一下,莫得问题~

$("div").css({"width":200,"height":200,'opacity':0.5});

css 扩展:$.cssHooks

比方说,未来的某一天,出现了新的样式属性 ,比方说 wh 吧,用来设置宽高的。我们希望通过
$("div").css("wh","200px"); 来一并设置div的宽高属性

jquery 也想到了这一点,为了扩展未来的属性,添加了静态属性:样式钩子$.cssHooks

通过上面的栗子我们知道 css 有两个核心功能:获取属性、设置属性

这个钩子主要也是处理这两个核心功能:分别是 getset 两种方法

其中getset方法是固定的,但是里面处理的逻辑,是开发者自己定义的,也就是 jquery 对未来单位的扩展。

// html
$.cssHooks['wh'] = {
    get(ele){
    	// 获取属性
        // console.log(ele);
        return ele.offsetWidth + " " + ele.offsetHeight;
    },
    set(ele,value){
    	// 设置属性 
        ele.style.width = value;
        ele.style.height = value;
    }
}

然后可以尝试使用一下:

// html
// 走 get 方法
let res = $("div").css("wh");
console.log(res);
// 走 set 方法
$("div").css("wh","200px");

那这样的源码如何写呢?

首先我们找自己的代码中,哪里走get ,哪里走set

  • 走get:获取属性的时候
  • 走set:设置属性的时候

JQuery 源码简单刨析,分析源码不再痛苦~_第6张图片

JQuery 源码简单刨析,分析源码不再痛苦~_第7张图片
至此,css 的扩展也就结束啦~

当然,如果你想让自己写的wh 属性不加单位,可以在静态属性 $.cssNumber 中添加以下

// html
$.cssNumber['th'] = true;

复盘总结

  • Jquerythis 的处理
  • 对未来的扩展:静态属性
  • 链式调用的思想
  • 封装的思想

碎碎念

这个教程我看了好久,但是就是没时间整理,最近比较堕落哈哈哈
工作了快小半年了,有点迷茫,哈哈但是这是必经之路吧,越早经历这些或许以后的路就越好走,太顺风顺水也没啥意思(我也想顺风顺水呀呜呜呜),只是小矫情罢了~

如果对你有帮助的话,来个赞吧~辛苦您啦

JQuery 源码简单刨析,分析源码不再痛苦~_第8张图片


代码

如下:

// myJquery.js
class myJquery {
    constructor(ele, root) {
        if (typeof root === "undefined") {
            this.prevObject = [document];
        } else {
            this.prevObject = root;
        }
        if (typeof ele === "string") {
            // 如果传入的是字符串的话
            let arr = document.querySelectorAll(ele);
            this.addElement(arr);
        } else {
            // 传入的如果是原生节点
            if (typeof ele.length == "undefined") {
                this[0] = ele;
                this.length = 1;
            } else {
                // 传入的是原生节点数组
                this.addElement(arr);
            }
        }
    }
    addElement(arr) {
        // 把每个节点挂到this上
        // 这里的 this 就是实例化的对象,就是下面 $ 函数返回的实例化对象
        arr.forEach((item, index) => {
            this[index] = item;
        });
        this.length = arr.length;
    }
    eq(index) {
        return new myJquery(this[index], this);
    }
    end() {
        return this.prevObject;
    }
    click(fn) {
        // 绑定事件
        for (let i = 0; i < this.length; i++) {
            this[i].addEventListener("click", fn, false);
        }
    }
    on(eventName, fn) {
        let reg = /\s+/g;
        // 去掉开头和结尾的空格,并且把中间的空格代替成一个空格,方便后续处理
        eventName = eventName.trimStart().trimEnd().replace(reg, " ");
        let arr = eventName.split(" ");
        // console.log(arr);
        for (let i = 0; i < this.length; i++) {
            for (let j = 0; j < arr.length; j++) {
                this[i].addEventListener(arr[j], fn, false);
            }
        }
    }
    css(...arg) {
        // 对于形参个数不固定,可以用 rest 参数来处理
        if (arg.length === 1) {
            if (typeof arg[0] === "string") {
                // 方式一:获取属性
                // ie6 的话可以用 currentStyle 解决兼容性问题
                if (arg[0] in $.cssHooks) {
                    // 只返回第一个元素的宽高
                    return $.cssHooks[arg[0]].get(this[0]);
                }
                return window.getComputedStyle(this[0], null)[arg[0]];
            } else {
                // 方式三:设置属性
                for (let i = 0; i < this.length; i++) {
                    for (let j in arg[0]) {
                        this.setStyle(this[i], j, arg[0][j]);
                    }
                }
            }
        } else {
            // 方式二:设置属性
            for (let i = 0; i < this.length; i++) {
                this.setStyle(this[i], arg[0], arg[1]);
            }
        }
    }
    setStyle(node, attr, val) {
        if (typeof val === "number" && !(attr in $.cssNumber)) {
            val = val + "px";
        }
        if (attr in $.cssHooks) {
            $.cssHooks[attr].set(node, val);
        } else {
            node.style[attr] = val;
        }
    }
}

function $(ele) {
    return new myJquery(ele);
}

$.cssNumber = {
    animationIterationCount: true,
    columnCount: true,
    fillOpacity: true,
    flexGrow: true,
    flexShrink: true,
    fontWeight: true,
    gridArea: true,
    gridColumn: true,
    gridColumnEnd: true,
    gridColumnStart: true,
    gridRow: true,
    gridRowEnd: true,
    gridRowStart: true,
    lineHeight: true,
    opacity: true,
    order: true,
    orphans: true,
    widows: true,
    zIndex: true,
    zoom: true,
};

$.cssHooks = {};

你可能感兴趣的:(#,精讲,javascript)