从零构建jQuery
第一步
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 null
}
}
return api
}
这样调用jQuery('.red')会返回api对象,
api对象中可以调用addClass,即jQuery('.red').addClass('blue')
代码逻辑:找到selector选择中的元素并赋值给elements,返回api对象,api对象中addClass方法对elements进行操作
第二步
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
}
}
return api
}
虽然只改变了addClass的返回值,但是这样就可以
jQuery('.red').addClass('blue').addClass('green')
代码逻辑:只要通过jQuery()函数调用,接下来你用不用addClass函数都会返回到api对象
第三步
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
}
}
return api
}
api调用的addClass
小知识:对象中addClass:function(){}等价于上面
第四步
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
}
}
}
只要你调用了jQuery最后你总会得到一个无名对象(api)里面有函数
现在我们用jQuery对象来指代jQuery函数创造出来的对象
第五步
实现find功能,之前四步实现了根据选择器找到元素并给找到的元素增加类名
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
},
find(selector){
let array = []
for(let i = 0;i<elements.length;i++){
array = array.concat(
Array.from(
elements[i].querySelectorAll(selector)
)
)
}
return this
}
}
}
现在可以实现jquery(".red").find(".blue")
注意:Array.from() 方法从一个类似数组或可迭代对象创建一个新的,以及querySelector在找不到时会返回null而不是空数组
第六步
第五步出现了问题,jquery(".red").find(".blue").addClass(".green")最后的green是add到elements上的而不是array上等价于jquery(".red").addClass(".green")
下面解决让addClass操作的是array
window.jQuery = function(selector){
let elements = document.querySelectorAll(selector)
return {
addClass(className){
for(let i = 0;i<elements.length;i++){
elements[i].classList.add(className)
}
return this
},
find(selector){
let array = []
for(let i = 0;i<elements.length;i++){
array = array.concat(
Array.from(
elements[i].querySelectorAll(selector)
)
)
}
elements = array
return this
}
}
}
首先我们想到就是修改elements
const api1 = jQuery('.test')
api1.addClass('blue')
const api2 = jQuery('.test').find('.child').addClass('.red')
api1.addClass('.green')
但是这样就会出现问题如以上代码不能在退回去用api1了
第七步
window.jQuery = function(selectorOrArray) {
let elements
if (typeof selectorOrArray === "string") {
elements = document.querySelectorAll(selectorOrArray);
} else if (selectorOrArray instanceof this.Array) {
elements = selectorOrArray;
}
return {
addClass(className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return this;
},
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 newApi;
}
};
};
理解关键就在于你的elements是什么你的api就在操作什么
代码逻辑:你使用jQuery('.test').find('.child')首先创建旧elements然后创建array,在旧elements中找到符合条件的赋值给array,然后将array赋值给新elements,那么现在在addClass就是给新elements加类了
如果我们把旧的elements对应的api保存下来那么我们就可以来回操作
第八步
window.jQuery = function(selectorOrArray) {
let elements;
if (typeof selectorOrArray === "string") {
elements = document.querySelectorAll(selectorOrArray);
} else if (selectorOrArray instanceof this.Array) {
elements = selectorOrArray;
}
return {
oldApi : selectorOrArray.oldApi,
addClass(className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return this;
},
find(selector) {
let array = [];
for (let i = 0; i < elements.length; i++) {
array = array.concat(
Array.from(elements[i].querySelectorAll(selector))
);
}
array.oldApi = this
return jQuery(array);
},
end(){
return this.oldApi
}
};
};
加入end()方法,之前把oldApi挂在array上面,array传给selectorOrArray,end()返回this.oldApi
第九步
window.jQuery = function(selectorOrArray) {
let elements;
if (typeof selectorOrArray === "string") {
elements = document.querySelectorAll(selectorOrArray);
} else if (selectorOrArray instanceof this.Array) {
elements = selectorOrArray;
}
return {
oldApi: selectorOrArray.oldApi,
addClass(className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return this;
},
find(selector) {
let array = [];
for (let i = 0; i < elements.length; i++) {
array = array.concat(
Array.from(elements[i].querySelectorAll(selector))
);
}
array.oldApi = this;
return jQuery(array);
},
end() {
return this.oldApi;
},
each(fn) {
for (let i = 0; i < elements.length; i++) {
fn.call(null, elements[i], i);
}
return this;
},
parent() {
const array = [];
this.each(node => {
if (array.indexOf(node.parentNode) === -1) {
array.push(node.parentNode);
}
});
array.oldApi = this
return jQuery(array);
},
children(){
const array = []
this.each((node)=>{
array.push(...node.children)
})
array.oldApi = this
return jQuery(array)
}
};
};
each函数,这种内调函数逻辑是这样的:each接受一个函数参数称为fn,fn在each内部调用,fn接受一个参数放你想放的东西,fn返回值或执行语句一定和这个参数有关这样调用才有意义
总结
- elementsOrArray只可以接受传来的值然后赋值给elements,不可以在方法里改变它要改只能返回新api,每一个elements对应一个api,api里包含着上一个api
- jQuery并不返回选择到的元素,而是返回可以操作这些元素的api