手寫 DOM 庫(kù)(2),上一次我們用對(duì)象風(fēng)格封裝DOM操作(原生js),這次用jQuery風(fēng)格重新封裝。
其它
1.window.jQuery=function(){}
jQuery是全局變量可以直接使用jQuery()
jQuery核心思想
接受一個(gè)selector。
根據(jù)這個(gè)選擇器得到一些元素。
return 一個(gè)對(duì)象。
這個(gè)對(duì)象有些方法可以操作這個(gè)元素。
2.舊語(yǔ)法key:value //"addClass":function(參數(shù)){}
ES6新語(yǔ)法 //addClass(參數(shù)) {}
3.聲明一個(gè)對(duì)象api,再return這個(gè)對(duì)象。
其實(shí)可以直接return這個(gè)對(duì)象!
4.當(dāng)變量聲明后只使用一次時(shí),可省略不用聲明。
5.array3 = array1.concat(array2); //concat里是偽數(shù)組
相當(dāng)于array3 = array1 + array2
concat方法創(chuàng)建一個(gè)新的數(shù)組,它由被調(diào)用的對(duì)象中的元素組成。
將偽數(shù)組變成數(shù)組 Array.from()
6.偽數(shù)組
7.const不能重復(fù)賦值,而且在聲明時(shí)必須賦值
可以用let
8.if (typeof selectorOrArray === 'string') {
...
} else if (selectorOrArray instanceof Array) { //x instanceof object
...
}
對(duì)象用instanceof
9.語(yǔ)法 arr.indexOf(searchElement[, fromIndex]) //fromIndex可選,示例略
indexOf()方法返回在數(shù)組中可以找到一個(gè)給定元素的第一個(gè)索引。
>=0表示存在,===-1表示不存在。
const beasts = ['ant', 'bison', 'camel', 'bison'];
console.log(beasts.indexOf('bison')); // 1
console.log(beasts.indexOf('giraffe')); // -1
10....展開操作符,可以把一個(gè)數(shù)組展開
...node.children
等同于
node.children[0],node.children[1],node.children[2]
第二種.用jQuery風(fēng)格重新封裝
完整代碼
鏈?zhǔn)斤L(fēng)格也叫jQuery風(fēng)格
window.jQuery()是我們提供的全局函數(shù)
特殊函數(shù)jQuery
jQuery(選擇器)用于獲取對(duì)應(yīng)的元素
但它卻不返回這些元素
相反,它返回一個(gè)對(duì)象,稱為jQuery構(gòu)造出來的對(duì)象
這個(gè)對(duì)象可以操作對(duì)應(yīng)的元素
jQuery核心思想
1.閉包 & 鏈?zhǔn)讲僮?/h3>
添加class
<div class="test">測(cè)試1</div>
<div class="test">測(cè)試2</div>
<div class="test">測(cè)試3</div>
window.jQuery = function (selector) { //接收1個(gè)選擇器#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對(duì)象
api.addClass('red') //遍歷所有獲取的元素,添加.red
閉包:函數(shù)訪問外部變量
鏈?zhǔn)讲僮?/h3>
第1步.addClass函數(shù) 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')//鏈?zhǔn)讲僮?
解析
方法,addClass函數(shù)里return api。
你用api調(diào)了一函數(shù)(addClass),這個(gè)函數(shù)返回前面的那個(gè)東西(api)。
這樣你就可以繼續(xù)在后面調(diào)addClass。這種操作就叫鏈?zhǔn)讲僮鳌?/strong>
this
obj.fn(p1)
obj.fn.call(obj,p1)
函數(shù)里的this就是obj
api.addClass('red').addClass('blue')
this就是api
addClass(className){
...
return this //api
}
聲明一個(gè)對(duì)象api,再return這個(gè)對(duì)象。其實(shí)可以直接return這個(gè)對(duì)象!
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
}
}
}
總結(jié)
jquery核心思想
1.用閉包維持elements
const api=jQuery('.test')
jQuery提供一個(gè)函數(shù),這個(gè)函數(shù)接收一個(gè)選擇器(css中的選擇器)。
根據(jù)選擇器獲取到這些元素,但是它不會(huì)返回給你這些元素,只會(huì)返回一個(gè)對(duì)象,這個(gè)對(duì)象會(huì)有一些方法(函數(shù)),由函數(shù)操作你的元素。
2.鏈?zhǔn)讲僮鱮eturn this
this是調(diào)用后才確定的!(未知的)
你在addClass前面?zhèn)鞯氖裁?,this就是什么。
const api = jQuery('.test')
api.addClass('red').addClass('blue')
//this就是api
變量聲明后只用一次時(shí),可以省略聲明
上面的代碼可以簡(jiǎn)寫為
jQuery('.test')
.addClass('red')
.addClass('blue')
jQuery對(duì)象
var obj=new Object()
Object就是構(gòu)造函數(shù)
jQuery是構(gòu)造函數(shù)嗎?
是,因?yàn)閖Query函數(shù)確實(shí)構(gòu)造出了一個(gè)對(duì)象
不是,因?yàn)椴恍枰獙憂ew jQuery()就能構(gòu)造一個(gè)對(duì)象,以前講的構(gòu)造函數(shù)都要結(jié)合new才行。
結(jié)論
jQuery是一個(gè)不需要加new的構(gòu)造函數(shù)
jQuery不是常規(guī)意義上的構(gòu)造函數(shù)
這是因?yàn)閖Query用了一些技巧(目前沒必要將)
jQuery對(duì)象代指jQuery函數(shù)構(gòu)造出來的對(duì)象(口頭約定)
只是口頭約定下,jquery是函數(shù)不是普通對(duì)象
術(shù)語(yǔ)
舉例
Object是個(gè)函數(shù),Object對(duì)象表示Object構(gòu)造出的對(duì)象
Array是個(gè)函數(shù),Array對(duì)象/數(shù)組對(duì)象表示Array構(gòu)造出來的對(duì)象
Function是個(gè)函數(shù),F(xiàn)unction對(duì)象/函數(shù)對(duì)象表示Function構(gòu)造出來的對(duì)象
鏈?zhǔn)斤L(fēng)格
查
1.jQuery('#xxx')返回值不是元素而是一個(gè)api對(duì)象
2.jQuery('#xxx').find('.red')查找#xxx里的.red元素
3.實(shí)現(xiàn)end函數(shù)
4.jQuery('.red').each(fn)遍歷并對(duì)每個(gè)元素執(zhí)行fn
5.jQuery('#xxx').parent()獲取爸爸
6.jQuery('#xxx').children()獲取兒子
1.略
2.jQuery('#xxx').find('.red')查找#xxx里的.red元素
this是api,閉包變量elements。
<div class="test"> //elements為3,3個(gè).test,一個(gè)一個(gè)遍歷
測(cè)試1
<div class="child">child1</div>
<div class="child">child2</div>
<div class="child">child3</div>
</div>
<div class="test">
測(cè)試2
<div class="child">child1</div>
<div class="child">child2</div>
</div>
<div class="test">
測(cè)試3
<div class="child">child1</div>
</div>
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)
解析:假設(shè)有多個(gè)selector選擇器元素
elements類似于數(shù)組,數(shù)組不能querySelectorAll。
遍歷elements數(shù)組(當(dāng)前有3個(gè)test數(shù)組),在數(shù)組里分別find子元素。
遍歷到child后,我們應(yīng)該操作child。如何操作才能確定操作到的是child而不是其它?
當(dāng)前是純數(shù)組arr,返回的也是數(shù)組return arr
數(shù)組不是函數(shù),不能直接操作。
Uncaught TypeError: x1.addClass is not a function
那return this可以嗎?不行
this 是當(dāng)前對(duì)象'api',api是操作elements的,它只能操作一個(gè)。因此不能操作arr!
接口
jQuery('.test')
.find('.child')
.addClass('fuck')
return this
return的是find前面的.test而不是我想操作的.child
只能重新封裝一個(gè)jQuery函數(shù),得到一個(gè)新的api來操作child
jQuery不能只接收選擇器selector還要能接收數(shù)組Array
封裝一個(gè)新的api,操作child。
之前接收的是selector,現(xiàn)在接收個(gè)數(shù)組。把數(shù)組給你,然后封裝個(gè)新api
結(jié)構(gòu)一樣,但保存的elements不同
步驟: 第1步,return由jQuery重新構(gòu)造出來的newApi。(不能直接return之前的api)
第2步,jQuery接收選擇器和數(shù)組(selectorOrArray)
第3步,如果是數(shù)組就等于"新的elements"
const elements在{}內(nèi)作用域有限,可以把它放到外面,作用域提升。由于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 可以直接簡(jiǎn)寫為
return jQuery(arr) //第1步,jQuery構(gòu)造出來的newApi
}
接口
jQuery('.test')
.find('.child')
.addClass('fuck')
jQuery構(gòu)造出來的newApi。
const newApi = jQuery(arr),參數(shù)arr傳入下面jQuery重新生成一個(gè)新arr數(shù)組。
window.jQuery = function (selectorOrArray) {}
3.實(shí)現(xiàn)end函數(shù)
jQuery('.test')
.find('.child')
.addClass('red')
.end() //回到上一次api
.addClass('fuck') //操作對(duì)象是.test而不是.child
用戶突然想回到上一次api操作test,如何實(shí)現(xiàn)?
oldApi: selectorOrArray.oldApi, //把oldApi復(fù)制到當(dāng)前api(之前在數(shù)組上)
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')
補(bǔ)充:數(shù)組是對(duì)象,對(duì)象可以加屬性。
this為什么會(huì)變?
幫助理解
const api1 = jQuery('.test')
const api2 = api1.find('.child').addClass('red')
const oldApi = api2.end().addClass('blue') //當(dāng)前為oldApi
oldApi放到數(shù)組上了并沒有放到api上,api是操作數(shù)組,this是api而不是arr。
應(yīng)該把oldApi復(fù)制過來
oldApi: selectorOrArray.oldApi,//把oldApi復(fù)制過來,
4.jQuery('.red').each(fn)遍歷并對(duì)每個(gè)元素執(zhí)行fn
each(fn) {
for (let i = 0; i < elements.length; i++) {//elements是閉包,會(huì)一直在上面
fn.call(null, elements[i], i) //不用this
}
return this //api對(duì)象
}
接口
const x = jQuery('.test').find('.child')
x.each((div) => console.log(div))
解析:
each(fn){}接收一個(gè)參數(shù)fn,x調(diào)用each時(shí)傳了個(gè)fn。
(div)=>console.log(div)就是傳進(jìn)去的參數(shù)fn。
each遍歷時(shí)會(huì)調(diào)用fn,fn在調(diào)用時(shí)傳了兩個(gè)參數(shù)。
fn.call(null,elements[i],i)
elements[i]是第1個(gè)參數(shù),i是第2個(gè)參數(shù)。
div就是第1個(gè)參數(shù),名字無所謂不會(huì)有任何影響,只是用來占位的、形式參數(shù)。
要習(xí)慣在一個(gè)函數(shù)(each)里再傳一個(gè)fn。在這個(gè)fn里拿到這個(gè)參數(shù)(div),這個(gè)參數(shù)實(shí)際上是在調(diào)用fn時(shí)傳給你的,并不是實(shí)際的div。
5.jQuery('#xxx').parent()獲取爸爸
用each實(shí)現(xiàn)更多的函數(shù)
parent() {
const array = []
this.each((node) => {
if (array.indexOf(node.parentNode) === -1) {//如果存在就不需要push
array.push(node.parentNode)
}
})
return jQuery(array) //返回可以操作數(shù)組的對(duì)象
},
print() {
console.log(elements)
}
接口
const x = jQuery('.test')
x.parent().print()
return array沒有可操作性,封裝個(gè)操作數(shù)組的對(duì)象jQuery(array),jQuery會(huì)返回個(gè)對(duì)象,對(duì)象會(huì)操作這些元素
6.jQuery('#xxx').children()獲取兒子
children() {
const array = []
this.each((node) => {
array.push(...node.children)
})
return jQuery(array)
}
接口
const x = jQuery('.test')
x.children().print()
...展開操作符,可以把一個(gè)數(shù)組展開
...node.children
等同于
node.children[0],node.children[1],node.children[2]
實(shí)現(xiàn)createElement、get、appendTo、append、
window.jQuery = function (selectorOrArrayOrTemplate) {
let elements
if (typeof selectorOrArrayOrTemplate === 'string') {
if (selectorOrArrayOrTemplate[0] === '<') {
// 創(chuàng)建 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,對(duì)每個(gè)el進(jìn)行node.appendChild操作
} else if (node.jquery === true) {
this.each(el => node.get(0).appendChild(el))
//遍歷elements,對(duì)每個(gè)el進(jìn)行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 = $('<div><span>1</span></div>')
const $childList = $('.child')
$('body').append($childList)