前言
jQuery的实现都是基于DOM API的,但是相比原生JavaScript的DOM操作更加便利,而且可以直接链式操作。它的基本设计思想是声明一个全局函数jQuery,使用别名$,对接受参数进行重载,获取对应的元素,之后便返回一个对象,称为jQuery构造出来的对象,再用这个对象去操作获取到的元素。
选择网页元素
jQuery使用$(selector)的方法实现获取网页元素的操作,
我们这里用DOM API的querySelector去实现它。
window.$ = window.jQuery = function(selector){
let elements = document.querySelectorAll(selector)
return {//返回一个操作elements的对象}
}
查找元素
获取了网页元素之后,怎么查找元素里面的内容呢?封装一个find函数,此时我们需要使用for循环来遍历里面的元素。
find(selector) {
let array = [];
for(let i=0;i<elements.length;i++){
array = array.concat(Array.from(elements[i].querySelectorAll(selector)))
}
return this
}
遍历函数
事实上,很多地方我们都需要去遍历$(selector)获取的元素,为何不直接封装一个each函数来直接调用呢?
each(fn) {
for(let i=0;i<elements.length;i++){
fn.call(null,elements[i])
}
}
当调用each函数后,就会传递elements的每一项给fn函数并调用。上面的函数可以用更简洁的代码实现
find(selector) {
let array = [];
each((item)=>array.concat(Array.from(item.querySelectorAll(selector))))
return this
}
jQuery实现重载
可以看到上面的函数返回的是当前执行环境的对象,也就是jQuery构造出来的对象。
如果我们继续执行链式操作的话,例如$('#id').find('.child').parent(),
此时parent操作的elements是$('#id')中的elements而不是find中的array,怎么办呢?
这里我们需要对$('#id')进行重载
window.$ = window.jQuery = function(selectorOrArray){
let elements
if(typeof selectorOrArray === 'string'){
elements = document.querySelectorAll(selectorOrArray)
}else if(selectorOrArray instanceof Array){
elements = selectorOrArray
}
return {//返回一个操作elements的对象}
}
现在我们把函数的返回值改为return jQuery(array)
就可以把find产生的array传给jQuery函数了,返回的是一个新的对象,
elements保存的是find函数产生的array,可以按预期地继续执行链式操作了。
返回上一个执行环境
进行jQuery链式操作的时候,我们若是要返回上一个执行环境怎么办?
例如$('#id').find('.child').parent().addClass()
我想addClass操作的是$('#id')里的elements要怎么做呢?
我们可以在进入下一个执行环境之前,保存当前执行环境的对象。
以find函数为例
find(selector) {
let array = [];
for(let i=0;i<elements.length;i++){
array = array.concat(Array.from(elements[i].querySelectorAll(selector)))
}
//把当前执行环境的对象保存到array里面
array.oldApi = this;
return jQuery(array);
}
当执行find函数进入下一个执行环境的时候,elements保存的是find返回的数组。
这个数组里面的oldApi属性保存的就是上一个执行环境的对象,
可是jQuery(array)返回的对象里并没有保存到这个对象的引用,因此,我们需要添加一个属性以保存对象引用。
oldApi:selectorOrArray.oldApi
现在,我们只要调用一个函数,让他返回oldApi对象就可以了。这个函数在jQuery是end()
end() {
return this.oldApi
}
在jQuery中使用原型
在执行链式操作$('#id').find('.child').parent().addClass()的时候,我们可以看到,
每一步都会创建一个新的API,每一个新的API都创建自己对应的find、Each等函数,
因此会引起内存使用暴涨,我们需要用原型来解决这个问题。
const api = Object.create(jQuery.prototype)
把jQuery函数的API放到它的prototype属性里面,在jQuery函数中创建一个对象api,这个对象的__proto__属性保存jQuery.prototype的引用。
给这个对象添加属性,分别保存elements和oldApi。
Object.assign(api, {
elements: elements,
oldApi: selectorOrArrayOrTemplate.oldApi
})
修改jQuery函数的返回值return api,这个时候我们每次调用jQuery函数的时候,都会去调用它的原型上的API而不会创建大量的对象了。