手写 DOM 库(2),上一次我们用对象风格封装DOM操作(原生js),这次用jQuery风格重新封装。
其它
1.window.jQuery=function(){}
jQuery是全局变量可以直接使用jQuery()
jQuery核心思想
接受一个selector。
根据这个选择器得到一些元素。
return 一个对象。
这个对象有些方法可以操作这个元素。
2.旧语法key:value //"addClass":function(参数){}
ES6新语法 //addClass(参数) {}
3.声明一个对象api,再return这个对象。
其实可以直接return这个对象!
4.当变量声明后只使用一次时,可省略不用声明。
5.array3 = array1.concat(array2); //concat里是伪数组
相当于array3 = array1 + array2
concat方法创建一个新的数组,它由被调用的对象中的元素组成。
将伪数组变成数组 Array.from()
6.伪数组
7.const不能重复赋值,而且在声明时必须赋值
可以用let
8.if (typeof selectorOrArray === 'string') {
...
} else if (selectorOrArray instanceof Array) { //x instanceof object
...
}
对象用instanceof
9.语法 arr.indexOf(searchElement[, fromIndex]) //fromIndex可选,示例略
indexOf()方法返回在数组中可以找到一个给定元素的第一个索引。
>=0
表示存在,===-1
表示不存在。
const beasts = ['ant', 'bison', 'camel', 'bison'];
console.log(beasts.indexOf('bison')); // 1
console.log(beasts.indexOf('giraffe')); // -1
10....展开操作符,可以把一个数组展开
...node.children
等同于
node.children[0],node.children[1],node.children[2]
第二种.用jQuery风格重新封装
完整代码
链式风格也叫jQuery风格
window.jQuery()是我们提供的全局函数
特殊函数jQuery
jQuery(选择器)用于获取对应的元素
但它却不返回这些元素
相反,它返回一个对象,称为jQuery构造出来的对象
这个对象可以操作对应的元素
jQuery核心思想
1.闭包 & 链式操作
添加class
测试1
测试2
测试3
window.jQuery = function (selector) { //接收1个选择器#test
const elements = document.querySelectorAll(selector)
//api可以操作elements
const api = {
addClass(className) {
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className) //闭包
}
return null
}
}
return api //操作elements的api
}
接口
const api = jQuery('.test') //返回api对象
api.addClass('red') //遍历所有获取的元素,添加.red
闭包:函数访问外部变量
链式操作
第1步.addClass函数 return api
第2步.api.addClass('red').addClass('blue')
const api = {
addClass(className) { //主要代码
...
return api
}
}
return api
接口
const api = jQuery('.test')
api.addClass('red').addClass('blue')//链式操作
解析
方法,addClass函数里return api。
你用api调了一函数(addClass),这个函数返回前面的那个东西(api)。
这样你就可以继续在后面调addClass。这种操作就叫链式操作。
this
obj.fn(p1)
obj.fn.call(obj,p1)
函数里的this就是obj
api.addClass('red').addClass('blue')
this就是api
addClass(className){
...
return this //api
}
声明一个对象api,再return这个对象。其实可以直接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
}
}
}
总结
jquery核心思想
1.用闭包维持elements
const api=jQuery('.test')
jQuery提供一个函数,这个函数接收一个选择器(css中的选择器)。
根据选择器获取到这些元素,但是它不会返回给你这些元素,只会返回一个对象,这个对象会有一些方法(函数),由函数操作你的元素。
2.链式操作return this
this是调用后才确定的!(未知的)
你在addClass前面传的什么,this就是什么。
const api = jQuery('.test')
api.addClass('red').addClass('blue')
//this就是api
变量声明后只用一次时,可以省略声明
上面的代码可以简写为
jQuery('.test')
.addClass('red')
.addClass('blue')
jQuery对象
var obj=new Object()
Object就是构造函数
jQuery是构造函数吗?
是,因为jQuery函数确实构造出了一个对象
不是,因为不需要写new jQuery()就能构造一个对象,以前讲的构造函数都要结合new才行。
结论
jQuery是一个不需要加new的构造函数
jQuery不是常规意义上的构造函数
这是因为jQuery用了一些技巧(目前没必要将)
jQuery对象代指jQuery函数构造出来的对象(口头约定)
只是口头约定下,jquery是函数不是普通对象
术语
举例
Object是个函数,Object对象表示Object构造出的对象
Array是个函数,Array对象/数组对象表示Array构造出来的对象
Function是个函数,Function对象/函数对象表示Function构造出来的对象
链式风格
查
1.jQuery('#xxx')返回值不是元素而是一个api对象
2.jQuery('#xxx').find('.red')查找#xxx里的.red元素
3.实现end函数
4.jQuery('.red').each(fn)遍历并对每个元素执行fn
5.jQuery('#xxx').parent()获取爸爸
6.jQuery('#xxx').children()获取儿子
1.略
2.jQuery('#xxx').find('.red')查找#xxx里的.red元素
this是api,闭包变量elements。
//elements为3,3个.test,一个一个遍历
测试1
child1
child2
child3
测试2
child1
child2
测试3
child1
find(selector) {
let arr = []
for (let i = 0; i < elements.length; i++) {
const elements2 = Array.from(elements[i].querySelectorAll(selector))
arr = arr.concat(elements2)
}
return arr
}
接口
const x1=jQuery('.test').find('.child')
console.log(x1)
解析:假设有多个selector选择器元素
elements类似于数组,数组不能querySelectorAll。
遍历elements数组(当前有3个test数组),在数组里分别find子元素。
遍历到child后,我们应该操作child。如何操作才能确定操作到的是child而不是其它?
当前是纯数组arr,返回的也是数组return arr
数组不是函数,不能直接操作。
Uncaught TypeError: x1.addClass is not a function
那return this可以吗?不行
this 是当前对象'api',api是操作elements的,它只能操作一个。因此不能操作arr!
接口
jQuery('.test')
.find('.child')
.addClass('fuck')
return this
return的是find前面的.test
而不是我想操作的.child
只能重新封装一个jQuery函数,得到一个新的api来操作child
jQuery不能只接收选择器selector还要能接收数组Array
封装一个新的api,操作child。
之前接收的是selector,现在接收个数组。把数组给你,然后封装个新api
结构一样,但保存的elements不同
步骤: 第1步,return由jQuery重新构造出来的newApi。(不能直接return之前的api)
第2步,jQuery接收选择器和数组(selectorOrArray)
第3步,如果是数组就等于"新的elements"
const elements在{}内作用域有限,可以把它放到外面,作用域提升。由于const必须赋值改用let
window.jQuery = function (selectorOrArray) { //第2步
let elements //作用域提升
if (typeof selectorOrArray === 'string') {
elements = document.querySelectorAll(selectorOrArray)
} else if (selectorOrArray instanceof Array) {//第3步
elements = selectorOrArray
}
return {
addClass(className) {...
},
find(selector) {
let arr = []
for (let i = 0; i < elements.length; i++) {
const elements2 = Array.from(elements[i].querySelectorAll(selector))
arr = arr.concat(elements2)
}
// const newApi = jQuery(arr)
// return newApi 可以直接简写为
return jQuery(arr) //第1步,jQuery构造出来的newApi
}
接口
jQuery('.test')
.find('.child')
.addClass('fuck')
jQuery构造出来的newApi。
const newApi = jQuery(arr),参数arr传入下面jQuery重新生成一个新arr数组。
window.jQuery = function (selectorOrArray) {}
3.实现end函数
jQuery('.test')
.find('.child')
.addClass('red')
.end() //回到上一次api
.addClass('fuck') //操作对象是.test而不是.child
用户突然想回到上一次api操作test,如何实现?
oldApi: selectorOrArray.oldApi, //把oldApi复制到当前api(之前在数组上)
find(selector) {
...
arr.oldApi = this //this是旧api
return jQuery(arr)
},
end() {
return this.oldApi //this是新的api2
}
接口
jQuery('.test')
.find('.child')
.addClass('red')
.end()
.addClass('fuck')
补充:数组是对象,对象可以加属性。
this为什么会变?
帮助理解
const api1 = jQuery('.test')
const api2 = api1.find('.child').addClass('red')
const oldApi = api2.end().addClass('blue') //当前为oldApi
oldApi放到数组上了并没有放到api上,api是操作数组,this是api而不是arr。
应该把oldApi复制过来
oldApi: selectorOrArray.oldApi,//把oldApi复制过来,
4.jQuery('.red').each(fn)遍历并对每个元素执行fn
each(fn) {
for (let i = 0; i < elements.length; i++) {//elements是闭包,会一直在上面
fn.call(null, elements[i], i) //不用this
}
return this //api对象
}
接口
const x = jQuery('.test').find('.child')
x.each((div) => console.log(div))
解析:
each(fn){}接收一个参数fn,x调用each时传了个fn。
(div)=>console.log(div)就是传进去的参数fn。
each遍历时会调用fn,fn在调用时传了两个参数。
fn.call(null,elements[i],i)
elements[i]是第1个参数,i是第2个参数。
div就是第1个参数,名字无所谓不会有任何影响,只是用来占位的、形式参数。
要习惯在一个函数(each)里再传一个fn。在这个fn里拿到这个参数(div),这个参数实际上是在调用fn时传给你的,并不是实际的div。
5.jQuery('#xxx').parent()获取爸爸
用each实现更多的函数
parent() {
const array = []
this.each((node) => {
if (array.indexOf(node.parentNode) === -1) {//如果存在就不需要push
array.push(node.parentNode)
}
})
return jQuery(array) //返回可以操作数组的对象
},
print() {
console.log(elements)
}
接口
const x = jQuery('.test')
x.parent().print()
return array没有可操作性,封装个操作数组的对象jQuery(array),jQuery会返回个对象,对象会操作这些元素
6.jQuery('#xxx').children()获取儿子
children() {
const array = []
this.each((node) => {
array.push(...node.children)
})
return jQuery(array)
}
接口
const x = jQuery('.test')
x.children().print()
...展开操作符,可以把一个数组展开
...node.children
等同于
node.children[0],node.children[1],node.children[2]
实现createElement、get、appendTo、append、
window.jQuery = function (selectorOrArrayOrTemplate) {
let elements
if (typeof selectorOrArrayOrTemplate === 'string') {
if (selectorOrArrayOrTemplate[0] === '<') {
// 创建 div
elements = [createElement(selectorOrArrayOrTemplate)]
} else {
// 查找 div
elements = document.querySelectorAll(selectorOrArrayOrTemplate)
}
} else if (selectorOrArrayOrTemplate instanceof Array) {
elements = selectorOrArrayOrTemplate
}
function createElement(string) {
const container = document.createElement("template");
container.innerHTML = string.trim();
return container.content.firstChild;
}
// api 可以操作elements
return {
jquery: true,
elements: elements,
get(index) {
return elements[index]
},
appendTo(node) {
if (node instanceof Element) {
this.each(el => node.appendChild(el))
//遍历elements,对每个el进行node.appendChild操作
} else if (node.jquery === true) {
this.each(el => node.get(0).appendChild(el))
//遍历elements,对每个el进行node.get(0).appendChild(el))操作
}
},
append(children) {
if (children instanceof Element) {
this.get(0).appendChild(children)
} else if (children instanceof HTMLCollection) {
for (let i = 0; i < children.length; i++) {
this.get(0).appendChild(children[i])
}
} else if (children.jquery === true) {
children.each(node => this.get(0).appendChild(node))
}
},
...
}
}
window.$ = window.jQuery
接口
const $div = $('1')
const $childList = $('.child')
$('body').append($childList)