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 = {};
 
 
  |