addClass()的实现
1.完整版思路
window.jQuery = function (selector) {
const elements = document.querySelectorAll(selector);
const api = {
addClass: function (className) {
//注意,传进来的选择器里面的元素可能不唯一,所以需要遍历,对每一个都进行addClass
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return undefined; //我达到了自己的需求:在某些元素里添加属性名。那么就已经完成了,所以不需要在return什么了
},
};
return api;
};
//使用如下:
const api = jQuery(".container");
api.addClass("red");
//思考:怎样实现链式操作,即直接在addClass后面继续addClass?
2.优化思路 (一)
接上段代码的思考, 答:只要让addClass执行完后返回前面的api即可,再加上之前所学的this知识可得,这时的api即this 第一次优化后,代码如下
window.jQuery = function (selector) {
const elements = document.querySelectorAll(selector);
const api = {
addClass: function (className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className);
}
return this; //为了能在addClass执行完后立马又能调用addClass
},
};
return api;
};
//使用如下:
const api = jQuery(".container");
api.addClass("red").addClass("green");
3.优化思路 (二)
既然我在jQuery函数里构造了一个api对象,然后最后我又返回了这个api,那么我是不是可以直接跳过这个api的名字而直接return
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;
},
};
};
const api = jQuery(".container");
api.addClass("red").addClass("green");
- 总结以上代码,使用到的知识:
a. 闭包。addClass函数使用了它这个函数外面的变量elements. ⅰ. 闭包变量,this 和elements
b. 链式操作的精髓:函数返回调用它的这个东西。 变成this.
c. this的理解,举例如下:
obj.fn(p1)
obj.fn.call(obj,p1)
find()的实现
注意点一:我要找一个元素,应该是在一个div里面找的,但是elements是整个document里面的元素。所以需要对每个document里面的元素再进行遍历一次 ,给elements2,这个是个伪数组,需要使用Array,from()转换成真数组
初步代码如下:
window.jQuery = function (selector) {
const elements = document.querySelectorAll(selector);
return {
find(selector) {
let array = [];
for (let i = 0; i < elements.length; i++) {
const elements2 =Array.from(elements[i].querySelectorAll(selector));
array = array.concat(elements2);
}
return array;
//这里就有个问题,返回的是数组,数组就没有addClass,也就没有链式操作了
//若改成return this,这个this是pai这个对象,这个对象是用来操作elements的
//所以不能操作array
},
};
};
1.优化思路一(错误的)
window.jQuery = function (selector) {
const elements = document.querySelectorAll(selector);
return {
find(selector) {
let array = [];
for (let i = 0; i < elements.length; i++) {
const elements2 =Array.from(elements[i].querySelectorAll(selector));
array = array.concat(elements2);
}
return this;
},
};
};
//使用:
const x1 = jQuery('.test').find('.child')
x1.addClass("red") //这个用了this的find,使用addClass之后是加到了test上面而不是child上面,而我们的需求就是加到child上面
2.优化思路二(重新封装上面代码,然后返得到一个新的api)
为了使得不影响elements,我们需要得到一个新的api用新的api来操作新的elements。这个新的api用jQuery来构造,这时候jQuery传入的参数就得做判断,如果传进来的是数组,那么我们就将数组传给elements,如果传进来的是选择器,那么我们就直接querySelectorAll查找所有元素。 这样就有不同的elements来供新api来操作,旧api不会受到影响。 优化后的代码如下:
window.jQuery = function (selectorOrArray) {
let elements;
if (typeof selectorOrArray === "string") { //重载,根据不同参数做不同操作
elements = document.querySelectorAll(selectorOrArray);
} else if (selectorOrArray instanceof Array) {
elements = selectorOrArray;
}
return {
addClass: function (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++) {
const elements2 = Array.from(elements[i].querySelectorAll(selector));
array = array.concat(elements2);
}
const newApi = jQuery(array);
return newApi;//这里可以简写,去掉中间这个newApi的变量名
},
};
};
总结以上代码,使用到知识如下:
a. 重载:判断传入的参数,做不同的操作
b. api操作elements,新方法需要返回新api来操作新elements
end()的实现
精髓在第22、27和9行 22行来历:find()函数里面的newApi是新的api,在使用find函数return之前,就是旧的api这个旧的api就是调用find时的api 【api1.find()】find里面的this就是我这里所说的旧的api1。记录这个return之前的this,就是上一步嘛,就是旧的api,给了数组。 27行来历:使用end函数的时候直接返回到这个旧的api,也就是使用find之前的位置。 这个时候运行会报错,是因为oldApi只给了数组array,没有给总的return的这个对象,所以end函数访问的时候访问不到, 9行来历:所以需要给这个对象添加一个oldApi的属性,值就是selectorOrArray.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,
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++) {
const elements2 = Array.from(elements[i].querySelectorAll(selector));
array = array.concat(elements2);
}
array.oldApi = this;
const newApi = jQuery(array);
return newApi;
},
end() {
return this.oldApi;
},
};
};
each()的实现 (回顾数组的foreach)
each(fn) {
for (let i = 0; i < elements.length; i++) {
fn.call(null, elements[i], i);
}
return this;
}
//使用:
const x = jQuery("#test").find(".child");
x.each((x, i) => console.log(x, i));
print()的实现
print() {
console.log(elements);
}
parent()的实现
parent() {
const array = [];
this.each((node) => {
if (array.indexOf(node.parentNode) === -1) {
array.push(node.parentNode);
}
});
return jQuery(array);
}
children()的实现
在push中直接node.children的话是直接得到一个数组里面分开的,如下图:

使用数组前面加 ...可以展开放在一个数组里
children() {
const array = [];
this.each((node) => {
array.push(...node.children);
//等价于:array.push(node.children[0],node.children[1],node.children[2])
//这样写不知道要写多少个,所以有了...这个符号用来展开数组里面的数组
});
return jQuery(array);
}
//使用:
const x = jQuery("#test");
x.children().print();
得到结果:

siblings()的实现 找兄弟,但是要排除自己
siblings() {
const array = [];
this.each((node) => {
const siblings2 = Array.from(node.parentNode.children).filter(
(n) => n !== node
);
array.push(...siblings2);
});
return jQuery(array);
}