手寫 DOM 庫(2)朴摊,上一次我們用對象風(fēng)格封裝DOM操作(原生js)丁逝,這次用jQuery風(fēng)格重新封裝钠右。
其它
1.window.jQuery=function(){}
jQuery是全局變量可以直接使用jQuery()
jQuery核心思想
接受一個(gè)selector艾蓝。
根據(jù)這個(gè)選擇器得到一些元素骨望。
return 一個(gè)對象牵囤。
這個(gè)對象有些方法可以操作這個(gè)元素爸黄。
2.舊語法key:value //"addClass":function(參數(shù)){}
ES6新語法 //addClass(參數(shù)) {}
3.聲明一個(gè)對象api,再return這個(gè)對象揭鳞。
其實(shí)可以直接return這個(gè)對象炕贵!
4.當(dāng)變量聲明后只使用一次時(shí),可省略不用聲明野崇。
5.array3 = array1.concat(array2); //concat里是偽數(shù)組
相當(dāng)于array3 = array1 + array2
concat方法創(chuàng)建一個(gè)新的數(shù)組称开,它由被調(diào)用的對象中的元素組成。
將偽數(shù)組變成數(shù)組 Array.from()
6.偽數(shù)組
7.const不能重復(fù)賦值,而且在聲明時(shí)必須賦值
可以用let
8.if (typeof selectorOrArray === 'string') {
...
} else if (selectorOrArray instanceof Array) { //x instanceof object
...
}
對象用instanceof
9.語法 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(選擇器)用于獲取對應(yīng)的元素
但它卻不返回這些元素
相反焰轻,它返回一個(gè)對象,稱為jQuery構(gòu)造出來的對象
這個(gè)對象可以操作對應(yīng)的元素
jQuery核心思想
1.閉包 & 鏈?zhǔn)讲僮?/h3>
添加class
<div class="test">測試1</div>
<div class="test">測試2</div>
<div class="test">測試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對象
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è)對象api,再return這個(gè)對象客冈。其實(shí)可以直接return這個(gè)對象旭从!
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ù)選擇器獲取到這些元素,但是它不會返回給你這些元素退疫,只會返回一個(gè)對象渠缕,這個(gè)對象會有一些方法(函數(shù)),由函數(shù)操作你的元素。
2.鏈?zhǔn)讲僮鱮eturn this
this是調(diào)用后才確定的!(未知的)
你在addClass前面?zhèn)鞯氖裁窗保瑃his就是什么亦鳞。
const api = jQuery('.test')
api.addClass('red').addClass('blue')
//this就是api
變量聲明后只用一次時(shí),可以省略聲明
上面的代碼可以簡寫為
jQuery('.test')
.addClass('red')
.addClass('blue')
jQuery對象
var obj=new Object()
Object就是構(gòu)造函數(shù)
jQuery是構(gòu)造函數(shù)嗎棒坏?
是燕差,因?yàn)閖Query函數(shù)確實(shí)構(gòu)造出了一個(gè)對象
不是,因?yàn)椴恍枰獙憂ew jQuery()就能構(gòu)造一個(gè)對象坝冕,以前講的構(gòu)造函數(shù)都要結(jié)合new才行徒探。
結(jié)論
jQuery是一個(gè)不需要加new的構(gòu)造函數(shù)
jQuery不是常規(guī)意義上的構(gòu)造函數(shù)
這是因?yàn)閖Query用了一些技巧(目前沒必要將)
jQuery對象代指jQuery函數(shù)構(gòu)造出來的對象(口頭約定)
只是口頭約定下,jquery是函數(shù)不是普通對象
術(shù)語
舉例
Object是個(gè)函數(shù)喂窟,Object對象表示Object構(gòu)造出的對象
Array是個(gè)函數(shù)测暗,Array對象/數(shù)組對象表示Array構(gòu)造出來的對象
Function是個(gè)函數(shù),F(xiàn)unction對象/函數(shù)對象表示Function構(gòu)造出來的對象
鏈?zhǔn)斤L(fēng)格
查
1.jQuery('#xxx')返回值不是元素而是一個(gè)api對象
2.jQuery('#xxx').find('.red')查找#xxx里的.red元素
3.實(shí)現(xiàn)end函數(shù)
4.jQuery('.red').each(fn)遍歷并對每個(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è)遍歷
測試1
<div class="child">child1</div>
<div class="child">child2</div>
<div class="child">child3</div>
</div>
<div class="test">
測試2
<div class="child">child1</div>
<div class="child">child2</div>
</div>
<div class="test">
測試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)前對象'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 可以直接簡寫為
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') //操作對象是.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ù)組是對象花吟,對象可以加屬性秸歧。
this為什么會變?
幫助理解
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)遍歷并對每個(gè)元素執(zhí)行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){}接收一個(gè)參數(shù)fn,x調(diào)用each時(shí)傳了個(gè)fn矾麻。
(div)=>console.log(div)就是傳進(jìn)去的參數(shù)fn纱耻。
each遍歷時(shí)會調(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ù),名字無所謂不會有任何影響甩牺,只是用來占位的蘑志、形式參數(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ù)組的對象
},
print() {
console.log(elements)
}
接口
const x = jQuery('.test')
x.parent().print()
return array沒有可操作性,封裝個(gè)操作數(shù)組的對象jQuery(array)波桩,jQuery會返回個(gè)對象戒努,對象會操作這些元素
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萤皂,對每個(gè)el進(jìn)行node.appendChild操作
} else if (node.jquery === true) {
this.each(el => node.get(0).appendChild(el))
//遍歷elements撒穷,對每個(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)