一、手写jQuery
我自己写的部分代码看我的github的jQuery项目
(一)准备工作
1. 新建文件夹DOM-1、文件index.html、文件main.js、文件jQuery.js
2. 初始化index.html
- 语言改成zh
- title改成“手写jQuery”
- 写个‘你好’
- 先引用jQuery.js
<script src="jQuery.js"></script> - 再引用main.js
<script src='main.js'></script>,因为main.js需要使用jQuery.js的代码
3. 初始化jQuery.js
创造jQuery是个全局函数
window.jQuery = function () {
console.log('我是jQuery')
}
4. 初始化main.js
- main.js里面就可以使用
jQuery了 jQuery()
5、用parcel预览效果
- 新建终端
parcel src/index.html- 打开网页,看见‘你好’
- 打开控制台看见'我是jQuery'
- 成功
(二)重点开始
1、写jQuery函数
(1)第一版代码:(闭包)傻瓜代码,为了让你知道最详细的过程
window.jQuery = function(selector) {
//1.jQuery是个全局函数,我们要给他一个选择器当参数
const elements = document.querySelectorAll(selector); //2.他会选出满足条件的节点,组成一个伪数组叫element
const api = {
//3.他会生成一个叫api的对象,这个对象里的每一个函数都和elements形成闭包,也就是每个函数都要用到elements
//用这个api对象里的任意一个函数都可以操作elements
addClass(className) {
//给我一个class的名字,我会遍历每个element中的元素,给他们都加上className
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
}
};
return api; //4.最后返回对象api,这个对象里的函数可以对选中的元素(elements)操作
};
//你想选出(操作)哪些元素,你就把对应的选择器给jQuery函数,然后他会给你可以操作这些元素的函数集合(api对象)
//我想对.test的元素操作,那么jQuery(.test)就是一个叫api的对象,他包含了所有可以对.test元素操作的函数
//jQuery(.test).addClass('yy') 这样就对所有.test元素加上个叫yy的class
(2)第二版代码:(链式操作)思考return啥
window.jQuery = function(selector) {
const elements = document.querySelectorAll(selector);
const api = {
addClass(className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return api //执行完函数后还是返回api,可以继续对elements操作
}
};
return api;
};
//jQuery(.test).addClass('yy') 这样就对所有.test元素加上个叫yy的class,但是返回值仍是api,也就是说我们可以继续对.test操作
//jQuery(.test).addClass('yy').addClass('bb')对所有.test元素加上个叫yy的class,因为返回值是api,我们仍可对所有.test元素操作加上bb的class
注意到,jQuery(.test).addClass('yy')的this就是jQuery(.test)也就是api。
jQuery(selector).addClass('yy')的this===jQuery(selector)===api,所以改成return this
window.jQuery = function(selector) {
const elements = document.querySelectorAll(selector);
const api = {
addClass(className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return this //执行完函数jQuery(selector).addClass(xxx)后还是返回this(=jQuery(selector)),还可以继续对elements操作
}
};
return api;
};
window.jQuery(selector)的this是window,不要晕倒了
(3)第三版代码:优化,把api弄掉节省内存,因为我们创建了api对象,又返回api对象,直接返回一个对象不就好吗(知道为啥前面要把return从api改成this了吧)
window.jQuery = function(selector) {
const elements = document.querySelectorAll(selector);
return {
addClass(className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return this;
}
};
};
(4)小总结
①jQuery(选择器)用于获取对应的元素
- 但它却不返回这些元素
- 相反,它返回一个对象,称为jQuery构造出来的对象(简称jQuery对象)
- 这个对象可以操作对应的元素
jQuery(.test) 返回 当前的JQuery对象
jQuery(.test).addClass('yy') 操作:给所有.test元素加上class:yy
返回:this===jQuery(.test) ----当前的JQuery对象
jQuery(.test).addClass('yy').addClass('bb') 操作:给所有.test元素加上class:bb
返回:this===jQuery(.test).addClass('yy') ----当前的JQuery对象
②jQuery是一个不需要加new的构造函数。
jQuery()能构造一个对象,而且不用加newjQuery不是常规意义上的构造函数,这是因为jQuery用了一些技巧(目前没必要知道)
③window.$ = window.jQuery以后用$()就行
④用$开头的名字给JQuery对象命名!const $div=jQuery(.test)易于和普通DOM标签区分!
2、写find()函数以及发现链式操作的问题
(1)第一版代码:写find函数
window.jQuery = function(selector) {
const elements = document.querySelectorAll(selector);
return {
//对elements操作
//一、查
//find函数用于找某些元素
find(selector) {
let array = []; //这个数组将储存结果元素
for (let i = 0; i < elements.length; i++) {
//对elements中每一个元素中来找其中的结果元素
array = array.concat(
Array.from(elements[i].querySelectorAll(selector))
);
//所有结果元素会成为伪数组,把伪数组变成真数组,在连接到array中去,成为新的array
}
return array;
},
};
};
那完了,链式结构中断了。jQuery('.test').find('.children')函数返回的是数组array,不是this了!如果返回this,那就是那个jQuery对象了,之后还是对.test对象操作,那你找.children干啥??
(2)第二版代码:修改jQuery函数
- 我们发现jQuery函数不能只接受选择器,必须要接受个数组成为elements
- 第一步:重载:当参数为字符串时为选择器,elements是选出的那些元素组成的伪数组;当参数为数组时,elements就是数组本身
- 第二步:需要数组成为新的jQuery对象时,就要重新用
jQuery(数组)成为新的jQuery对象,然后return他;否则直接return this返回当前的JQuery对象就行
window.jQuery = function (selectorOrArray) {
let elements //const声明时必须赋值而且还不能改,所以只能用let了//不可以在下面elements前声明,不然就只在那个块级作用域里有用了
if (typeof selectorOrArray === 'string') { //如果是字符串,那就是选择器,选出对应元素
elements = document.querySelectorAll(selectorOrArray)
} else if (selectorOrArray instanceof Array) { //如果是数组,那就是它本身
elements =selectorOrArray
}
return {
//对elements操作
find(selector) {
let array = [];
for (let i = 0; i < elements.length; i++) {
array = array.concat(
Array.from(elements[i].querySelectorAll(selector))
);
}
const newApi = jQuery(array) //我们不可以return之前的api了,不然你find出这些元素干啥?肯定要对find出的元素搞事情了啊。//所以发现jQuery不能只接受选择器,还要有数组
//生成全新的jQuery对象了(专属这个数组里元素的函数集合)
return newApi;
//前两句请合并为 return jQuery(array)
},
addClass(className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return this;
}
};
};
3、end函数:链式问题还没解决:我还想回到之前的旧的jQuery对象咋办?
- 在新建数组的JQuery对象
jQuery(数组)前,要先把当前的JQuery对象加进数组里,成为一个新属性oldApi - 给JQuery对象加一个新属性
oldApi:selectorOrArray.oldApi - 写
end函数:返回当前jQuery对象的oldApi
window.jQuery = function(selectorOrArray) {
let elements;
if (typeof selectorOrArray === "string") {
elements = document.querySelectorAll(selectorOrArray);
} else if (selectorOrArray instanceof Array) {
elements = selectorOrArray;
}
return {
oldApi: selectorOrArray.oldApi, //给JQuery对象加一个属性,值为selectorOrArray的oldApi属性
find(selector) {
let array = [];
for (let i = 0; i < elements.length; i++) {
array = array.concat(
Array.from(elements[i].querySelectorAll(selector))
);
}
array.oldApi = this; //先把当前的jQuery对象放到即将要成为新的elements的数组中去,成为一个oldApi属性
return jQuery(array); //在返回 新建的 数组的jQuery对象
},
end() {
return this.oldApi;
}, //this为数组的JQuery对象了,他有个oldApi属性,属性值为selectorOrArray(也就是数组)的属性oldApi
addClass(className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return this;
}
};
};
api1=jQuery('.test')
api2=jQuery('.test').find('.children')
api2.end()//返回this的oldApi。
//this===api2===当前的JQuery对象。
//oldApi:selectorOrArray.oldApi=array.oldApi=(当时的this)=(当时的JQuery对象api2)
二、总结
1、jQuery(选择器)用于获取对应的元素,但它却不返回这些元素
- 相反,它返回一个对象,称为jQuery构造出来的对象(简称jQuery对象)
- 这个对象可以操作对应的元素
jQuery(.test) 返回 当前的JQuery对象
jQuery(.test).addClass('yy') 操作:给所有.test元素加上class:yy
返回:this===jQuery(.test) ----当前的JQuery对象
jQuery(.test).addClass('yy').addClass('bb') 操作:给所有.test元素加上class:bb
返回:this===jQuery(.test).addClass('yy') ----当前的JQuery对象
2、jQuery是一个不需要加new的构造函数。
- jQuery()能构造一个对象,而且不用加new
- jQuery不是常规意义上的构造函数,这是因为jQuery用了一些技巧(目前没必要知道)
3、window.$ = window.jQuery以后用$()就行
4、用$开头的名字给JQuery对象命名!
const $div=jQuery(.test)易于和普通DOM标签区分!
三、常用API
(具体代码不用管了,只需知道大体思路:调用DOM的API再加上些逻辑语句)
查
jQuery('#xxx)返回值并不是元素,而是一个api对象jQuery(" #xxx').find('.red')查找#xxx里的.red元素jQuery('#xxx').parent()获取爸爸jQuery('#xxx").children()获取儿子jQuery(' #xxx' ).siblings()获取兄弟jQuery(' #xxx ).index()获取排行老几(从0开始)jQuery(" #xxx' ).next()获取弟弟jQuery(' #xxx ).prev()获取哥哥jQuery(.red').each(fn)遍历并对每个元素执行fnjQuery('#xxx).get(index)获取elements的下标为index的元素
增
$('<div><span> 1</span></div> ).appendTo(...)把这串HTML加入...中$('body')获取document.body$('body').append(div>l</div>)添加小儿子$('body ).append('<div>1</div>)更方便$(' body ).prepend(div或$div)添加大儿子$('#test').after(div或$div)添个弟弟$('#test').before(div或$div)添个哥哥
删
$div.remove()$div.empty()删子元素
改
$div.text(?)读写文本内容$div.html(?)读写HTML内容$div.attr('title',?)读写属性$div.css({color: 'red'})读写style // $div.style更好$div.addClass('blue')/removeClass/hasClass$div.on('click', fn)$div.off('click, fn)
四、使用原型优化代码,节约内存
- 新建一个对象#619,把JQuery对象的所有共有函数放入这个新对象----原型
- 原来的JQuery对象里面的
__proto__保存原型的地址#619 - JQuery函数的
prototype保存他要生成的对象——JQuery对象的原型的地址#619 - 也就是,把共有属性(函数)放到JQuery函数的
prototype,然后jQuery.fn = jQuery.prototype,然后让api.__proto__指向$.fn - 注意,此时在JQuery函数里定义的变量
elements的作用域只在JQuery函数里,所以jQuery.prototype里定义的函数用不到elements,必须全改为this.elements
window.$=window.jQuery = function(selectorOrArrayOrtemplat) { //jQuery太长,用$代替
let elements;
if (typeof selectorOrArray === "string") {
elements = document.querySelectorAll(selectorOrArray);
} else if (selectorOrArray instanceof Array) {
elements = selectorOrArray;
}
//加入共有属性
const api = Object.create(jQuery.prototype); //创建个名为api的对象,这个对象的__proto__为jQuery.prototype
//相当于const api = {__proto__:jQuery.prototype}
//加入自身属性
/*小白写法
api.elements = elements;
api.oldApi = selectorOrArray.oldApi;
*/
//大师写法:Object.assign()表示把后面这些属性一个一个的加入api对象
Object.assign(api, {
elements = elements,
oldApi = selectorOrArray.oldApi
})
return api;
};
jQuery.fn = jQuery.prototype = { //prototype太长,用fn代替
//共有属性
constructor: jQuery, //必须得要,这是规定
oldApi: selectorOrArray.oldApi,
jquery: true,
get(index){
return this.elements[index];
}
find(selector) {},
end() {},
addClass(className) {},
each(fn) {},
parent() {},
children() {},
siblings() {},
index() {},
next() {},
prev() {}
};
五、设计模式
(一)jQuery用到了哪些设计模式
- 不用new的构造函数,这个模式没有专门的名字
$(支持多种参数),这个模式叫做重载(一个函数支持的参数多种多样)- 用闭包隐藏细节,这个模式没有专门的名字
$div.text()即可读也可写,getter / setter$.fn``是$.prototype的别名,这叫别名- jQuery针对不同浏览器使用不同代码,这叫适配器模式
(二)设计模式是啥
- 老子这个代码写得太漂亮了,别人肯定也用得到
- 那就给这种写法取个名字吧,
- 比如适配器模式
- 设计模式就是对通用代码取个名字而已
(三)我应该学习设计模式吗?
- 设计模式不是用来学的
- 你看了这些代码,但你并不知道这代码用来解决什么问题,看了白看
- 设计模式是用来总结的
- 你直管去写代码
- 把你的代码尽量写好,不断重写
- 总结你的代码,把写得好的地方抽象出来
- 看看符合哪个设计模式
- 你就可以告诉别人你用到了这几个设计模式
- 显得你特别高端
(三)需要学jQuery!
- 真相
- jQuery这么简单、经典的库为什么不学?
- 通过jQuery可以学会很多封装(把一个变量放到函数里,然后暴露出一个API,这个API可以操作这个变量)技巧,为什么不学?
- 连jQuery都理解不了,Vue / React肯定学不好
- 推荐文章 《jQuery都过时了,那我还学它干嘛?》
- 学习路线
- 理解jQuery原理(本篇博客就是)
- 使用jQuery做一两个项目
- 再总结篇博客(本篇博客+参考阮一峰+参考jQuery API 中文文档),然后再也不碰jQuery了
- 滚去学Vue / React,找工作要紧