Jquery 源码浅谈
开题吐槽
有人上来就说了:啊啊这都 0202 年了,怎么还学 JQuery
呢,远古时代么?
其实写这篇文章的目的呢,主要就是目前在复习 面向对象 的知识,通过分析 JQuery
源码的思想,可以帮助我更好的理解面向对象~
而且我原先还真没看过JQ的源码,借此跟大家分享一下,也请大家批评指正!
教程是基于开课吧的课程,自己整理出的笔记!!!
Hello Jquery
首先看一个例子:我想给 div
元素添加点击事件,那么我们可以用 JQ
这样写
1 2 3
| $('div').click(function () { console.log('点击了box1') })
|
我们有没有思考过,Jquery
怎样帮助我们实现这个功能的呢?
我们先来分析一下:$('.box1')
是一个 Jquery
对象,然后调用对象中的 click
方法,方法里传递了一个匿名函数!
知道了这些,就可以开始写一个自己的 jq
了来,vscode
上号!
首先新建一个自己的 myJquery.js
,然后 cv 大法。
1 2 3 4 5 6 7 8 9 10 11 12
| function $(arg) { return { click(fn) { fn() }, }; }
|
当然这个是不完整的,因为我还没有绑定事件呢,别着急,咱们一点一点来~
首先我要做一个简单的优化,因为这样写感觉有点乱,写成类的话看起来更清爽些(写成类的话注意兼容性的问题,我这里没有考虑兼容性)
1 2 3 4 5 6 7 8 9 10 11 12
| class myJquery{ constructor(){} click(){ console.log("点击事件") } } function $(ele){ return new myJquery(ele) }
|
然后咱们完善一下点击事件的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class myJquery{ constructor(ele){ this.ele = document.querySelectorAll(ele); } click(){ this.ele.forEach(item=>{ item.addEventListener('click',function(){ console.log(this) }) }) } }
|
这样虽然能够实现功能,但是 Jquery
本身并不是这样搞的
我们可以打印一下 Jquery
对象本身,然后再打印一下我们写的 myJquery
对象本身,对比一下
我们看到,Jquery 把每个元素挂到了对象上,像一个伪数组对吧~,并且有 length
和 prevObject
属性
看到学霸的答案了,咱们边抄边想为啥这么设计~
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class myJquery { constructor(ele) { let arr = document.querySelectorAll(ele); arr.forEach((item, index) => { this[index] = item; }); this.length = arr.length; } } function $(ele) { return new myJquery(ele); }
|
写完之后,打印一下咱们的节点:
看,是不是比较像jQuery
对象了,不过还少一个属性 prevObject
,待会儿会讲到的(在 end 封装
中)
接着把添加节点的那一坨代码提取到 addElement
中去,(为了使代码更清晰),然后再把点击事件的逻辑补充一下~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class myJquery { constructor(ele) { let arr = document.querySelectorAll(ele); this.addElement(arr); } addElement(arr) { 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
简单的源码咱就搞定了!
至于为什么这么设计呢,我个人理解的哈,有以下的好处
- 方便查找:我们把找到的节点挂到实例化的对象上,以后想查一下这个
Jquery
对象选了谁,直接 console.log($("节点"))
即可,而你不挂到this上,this上就是空空的,没有东西的。
- 方便源码的开发:比方说
eq
,on
,这类方法,我们开发源码的时候,如果this
上有对象的话,直接for
循环遍历添加功能即可。
当然这只是我的愚见,多年以后回过头来再看看估计是另外一种感悟吼吼吼。
鸡汤总结:研究某一个框架,先去研究它 宏观如何实现,理解它的实现思想,再去研究如何实现的
链式操作基础
jq
是有链式调用的,就是跟链条一样点啊点的,举个例子
1 2 3 4
| $("div").eq(0).click(function(){ console.log(2222); })
|
那我们怎样实现呢?
首先看一下对象如何链式调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| let obj = { fn1() { console.log("执行了 fn1") return this; }, fn2() { console.log("执行了 fn2") return this; } } obj.fn1().fn2().fn1();
|
观察代码,主要还是把 this 给返还了,this 是实例化对象,也就是 obj
接下来的几个封装方法会用到它的!
封装
eq 封装
功能描述:eq(index)
拿到集合中的某一个
1 2 3 4
| eq(index) { return new myJquery(this[index]); }
|
end 封装
jQuery
中还有个 end
方法,作用是返回上一个操作节点
比方说我 $("div").eq(1)
之后还想拿到前面的$("div")
,就可以这样操作:$("div").eq(1).end()
主要的思想就是:当前 jquery
对象有个 prevObject
属性,用来保存上一个操作的节点,所以end
操作其实没干什么,就是把 prevObject
返还给你。
1 2 3 4
| end() { return this['prevObject']; }
|
end
的这个操作,需要在初始化的时候处理一下,默认的前一个对象是 document
1 2 3 4 5 6 7 8
| constructor(arg, root) { if (typeof root === 'undefined') { this['prevObject'] = [document]; } else { this['prevObject'] = root; } }
|
on 封装
jQuery
中的 on
方法,可以写很多个事件,比方说
1 2 3 4
| $(".box1").on(" mouseover mousedown ",function(){ console.log("鼠标事件"); })
|
有个小优化,这里的字符串里可能有很多空格,就像上面的栗子那样
这里处理一下字符串,然后遍历绑定即可。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 封装
jq
中调用 css
有如下几种方式
- 获取属性:“
$("div").css("background")
- 设置属性1:
$("div").css("width",200);
- 设置属性2:
$("div").css({"width":"200px","height":"200px",'opacity':0.5 });
可以看出他们的区别:
- 获取属性:只需要传递一个字符串
- 设置属性
- 设置属性1:传递两个参数
- 设置属性2:传递一个对象
我们可以根据区别,然后做不同的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| css(...arg) { if (arg.length === 1) { if (typeof arg[0] === "string") { 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
里调用一下
1
| $("div").css({"width":"200px","height":"200px",'opacity':0.5});
|
嗯,看起来没啥大问题~
css 扩展:$.cssNumber
其实在 jquery
设置属性的时候,有些属性值你不写 px
,它也会帮你处理,比方说
1 2
| $("div").css("width",200)
|
所以,我们的代码还可以优化一下,在设置样式属性的时候,判断一下是否是数字
1 2 3 4 5
| setStyle(ele,styleName,styleValue){ if(typeof styleValue === 'number'){ styleValue = styleValue + "px"; } }
|
这样处理了,到底好不好呢?
其实并不好,因为像 opacity
这样的属性,他就 没有单位
那么jquery
是如何处理的呢?
这里设置了静态属性 $.cssNumber
:用来存放一些不需要加单位的样式属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| $.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, };
|
如果某一个属性不需要单位,我们可以这样判断一下
1 2 3 4 5 6 7
| setStyle(node, attr, val) { if(typeof val === 'number' && !(attr in $.cssNumber)){ val = val + 'px'; } node.style[attr] = val; }
|
然后在测试一下,莫得问题~
1
| $("div").css({"width":200,"height":200,'opacity':0.5});
|
css 扩展:$.cssHooks
比方说,未来的某一天,出现了新的样式属性 ,比方说 wh
吧,用来设置宽高的。我们希望通过
$("div").css("wh","200px");
来一并设置div的宽高属性
jquery
也想到了这一点,为了扩展未来的属性,添加了静态属性:样式钩子$.cssHooks
通过上面的栗子我们知道 css
有两个核心功能:获取属性、设置属性
这个钩子主要也是处理这两个核心功能:分别是 get
和 set
两种方法
其中get
和set
方法是固定的,但是里面处理的逻辑,是开发者自己定义的,也就是 jquery
对未来单位的扩展。
1 2 3 4 5 6 7 8 9 10 11 12 13
| $.cssHooks['wh'] = { get(ele){ return ele.offsetWidth + " " + ele.offsetHeight; }, set(ele,value){ ele.style.width = value; ele.style.height = value; } }
|
然后可以尝试使用一下:
1 2 3 4
|
let res = $("div").css("wh"); console.log(res);
|
1 2
| $("div").css("wh","200px");
|
那这样的源码如何写呢?
首先我们找自己的代码中,哪里走get ,哪里走set
- 走get:获取属性的时候
- 走set:设置属性的时候
至此,css 的扩展也就结束啦~
当然,如果你想让自己写的wh
属性不加单位,可以在静态属性 $.cssNumber
中添加以下
1 2
| $.cssNumber['th'] = true;
|
复盘总结
Jquery
对 this
的处理
- 对未来的扩展:静态属性
- 链式调用的思想
- 封装的思想
碎碎念
这个教程我看了好久,但是就是没时间整理,最近比较堕落哈哈哈
工作了快小半年了,有点迷茫,哈哈但是这是必经之路吧,越早经历这些或许以后的路就越好走,太顺风顺水也没啥意思(我也想顺风顺水呀呜呜呜),只是小矫情罢了~
如果对你有帮助的话,来个赞👍吧~辛苦您啦
代码
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| 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) { 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(" "); 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) { if (arg.length === 1) { if (typeof arg[0] === "string") { 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 = {};
|