有人上来就说了:啊啊这都 0202 年了,怎么还学
JQuery
呢,远古时代么?
其实写这篇文章的目的呢,主要就是目前在复习 面向对象 的知识,通过分析 JQuery
源码的思想,可以帮助我更好的理解面向对象~
而且我原先还真没看过JQ的源码,借此跟大家分享一下,也请大家批评指正!
教程是基于开课吧的课程,自己整理出的笔记!!!
首先看一个例子:我想给 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
本身并不是这样搞的
我们可以打印一下 Jquery
对象本身,然后再打印一下我们写的 myJquery
对象本身,对比一下
console.log($("div"));
我们看到,Jquery 把每个元素挂到了对象上,像一个伪数组对吧~,并且有 length
和 prevObject
属性
看到学霸的答案了,咱们边抄边想为啥这么设计~
// 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
对象了,不过还少一个属性 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
对象选了谁,直接 console.log($("节点"))
即可,而你不挂到this上,this上就是空空的,没有东西的。eq
,on
,这类方法,我们开发源码的时候,如果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(index)
拿到集合中的某一个
// myJquery.js
eq(index) {
return new myJquery(this[index]); // 返还对象
}
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;
}
}
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);
}
}
}
jq
中调用 css
有如下几种方式
$("div").css("background")
$("div").css("width",200);
$("div").css({"width":"200px","height":"200px",'opacity':0.5 });
可以看出他们的区别:
我们可以根据区别,然后做不同的处理
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
设置属性的时候,有些属性值你不写 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});
比方说,未来的某一天,出现了新的样式属性 ,比方说
wh
吧,用来设置宽高的。我们希望通过
$("div").css("wh","200px");
来一并设置div的宽高属性
jquery
也想到了这一点,为了扩展未来的属性,添加了静态属性:样式钩子$.cssHooks
通过上面的栗子我们知道 css
有两个核心功能:获取属性、设置属性
这个钩子主要也是处理这两个核心功能:分别是 get
和 set
两种方法
其中get
和set
方法是固定的,但是里面处理的逻辑,是开发者自己定义的,也就是 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
当然,如果你想让自己写的wh
属性不加单位,可以在静态属性 $.cssNumber
中添加以下
// html
$.cssNumber['th'] = true;
Jquery
对 this
的处理这个教程我看了好久,但是就是没时间整理,最近比较堕落哈哈哈
工作了快小半年了,有点迷茫,哈哈但是这是必经之路吧,越早经历这些或许以后的路就越好走,太顺风顺水也没啥意思(我也想顺风顺水呀呜呜呜),只是小矫情罢了~
如果对你有帮助的话,来个赞吧~辛苦您啦
如下:
// 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 = {};