目標(biāo):用 jQuery 風(fēng)格封裝 DOM
閉包 & 鏈?zhǔn)讲僮?/h3>
鏈?zhǔn)斤L(fēng)格
- 也叫 jQuery 風(fēng)格
- window.jQuery() 是我們提供的全局函數(shù)
- 特殊函數(shù) jQuery
- jQuery(選擇器) 用于獲取對(duì)應(yīng)的元素
- 但是它不返回這些元素
- 相反,它返回一個(gè)對(duì)象,稱(chēng)為 jQuery 構(gòu)造出來(lái)的對(duì)象
- 這個(gè)對(duì)象可以操作對(duì)應(yīng)的元素
代碼風(fēng)格的形成
- 我們想獲取一個(gè)DOM元素,自己封裝一個(gè)jQuery函數(shù),最開(kāi)始的做法是直接return獲取到的元素
window.jQuery = function(selector){
const elements = document.querySelectorAll(selector)
return elements
}
-
關(guān)鍵:但是 jQuery 的風(fēng)格就是沒(méi)有直接 return elements,而是 return 一個(gè)對(duì)象,并且這個(gè)對(duì)象具有一些方法,比如添加屬性的方法,這個(gè)新生成的api可以操作elements,并且這個(gè)jQuery函數(shù),return的剛好就是api
window.jQuery = function(selector){
const elements = document.querySelectorAll(selector)
// api 可以操作這個(gè)elements
const api = {
// ES6 的簡(jiǎn)寫(xiě)形式,之前是"addClass": function(className){}
addClass(className){
for(let i=0;i<elements.length;i++){
elements[i].classList.add(className)
}
return undefined
}
}
return api
}
-
閉包:上面這段代碼使用了閉包(即函數(shù)訪問(wèn)了外部的變量)
- 上面這段代碼返回的是api,而不是返回的是elements
- 當(dāng)我們調(diào)用上面的接口的時(shí)候,會(huì)給元素添加屬性
const api = jQuery('.test') // 不返回元素們,返回 api 對(duì)象
api.addClass('red') // 遍歷所有剛才獲取的元素,添加 .red
-
關(guān)鍵:我們之前的代碼是 api 里面的函數(shù)對(duì)象,返回的undefined,那么能不能返回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)
}
// 直接返回這個(gè)對(duì)象本身
return api
}
}
return api
}
-
鏈?zhǔn)讲僮?/strong>:這意味著我們?cè)谑褂昧薬pi的對(duì)象函數(shù)后,拿到的還是一個(gè)api對(duì)象,那么這樣就可以進(jìn)行鏈?zhǔn)讲僮?
const api = jQuery('.test') // 不返回元素們,返回 api 對(duì)象
api.addClass('red').addClass('blue').addClass('green') // 鏈?zhǔn)讲僮?
- 我們之前有一個(gè)概念:函數(shù)如果使用一個(gè)對(duì)象來(lái)調(diào)用,那么這個(gè)函數(shù)的this就是調(diào)用函數(shù)的對(duì)象,也就是obj.fn(p1)等價(jià)于obj.fn.call(obj, p1);我們針對(duì)這個(gè)概念對(duì)代碼進(jìn)行改進(jìn)
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)
}
// 改進(jìn)
return this
}
}
// 這邊不能動(dòng)
return 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
}
}
}
- 總結(jié):jQuery的核心思想是
- 閉包:提供一個(gè)函數(shù),這個(gè)函數(shù)接收一個(gè)選擇器,jQuery將根據(jù)選擇器獲取這些元素,但是不會(huì)去返回元素,而是返回對(duì)象,對(duì)象中有對(duì)應(yīng)的方法,這些方法來(lái)操作元素,jQuery用閉包去維持elements與函數(shù)
- 鏈?zhǔn)讲僮鳎汉瘮?shù)在調(diào)用的時(shí)候充分利用了this形成了鏈?zhǔn)讲僮?/li>
實(shí)現(xiàn) find 函數(shù)
- window.jQuery() 是我們提供的全局函數(shù)
- jQuery(選擇器) 用于獲取對(duì)應(yīng)的元素
- 但是它不返回這些元素
- 相反,它返回一個(gè)對(duì)象,稱(chēng)為 jQuery 構(gòu)造出來(lái)的對(duì)象
- 這個(gè)對(duì)象可以操作對(duì)應(yīng)的元素
window.jQuery = function(selector){
const elements = document.querySelectorAll(selector)
return elements
}
window.jQuery = function(selector){
const elements = document.querySelectorAll(selector)
// api 可以操作這個(gè)elements
const api = {
// ES6 的簡(jiǎn)寫(xiě)形式,之前是"addClass": function(className){}
addClass(className){
for(let i=0;i<elements.length;i++){
elements[i].classList.add(className)
}
return undefined
}
}
return api
}
const api = jQuery('.test') // 不返回元素們,返回 api 對(duì)象
api.addClass('red') // 遍歷所有剛才獲取的元素,添加 .red
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)
}
// 直接返回這個(gè)對(duì)象本身
return api
}
}
return api
}
const api = jQuery('.test') // 不返回元素們,返回 api 對(duì)象
api.addClass('red').addClass('blue').addClass('green') // 鏈?zhǔn)讲僮?
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)
}
// 改進(jìn)
return this
}
}
// 這邊不能動(dòng)
return api
}
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
}
}
}
- 閉包:提供一個(gè)函數(shù),這個(gè)函數(shù)接收一個(gè)選擇器,jQuery將根據(jù)選擇器獲取這些元素,但是不會(huì)去返回元素,而是返回對(duì)象,對(duì)象中有對(duì)應(yīng)的方法,這些方法來(lái)操作元素,jQuery用閉包去維持elements與函數(shù)
- 鏈?zhǔn)讲僮鳎汉瘮?shù)在調(diào)用的時(shí)候充分利用了this形成了鏈?zhǔn)讲僮?/li>
我們通常所說(shuō)的jQuery對(duì)象指的是jQuery構(gòu)造出來(lái)的對(duì)象
jQuery是構(gòu)造函數(shù)嗎?
- 是
- 因?yàn)?jQuery函數(shù)確實(shí)構(gòu)造出了一個(gè)對(duì)象
- 不是
- 因?yàn)椴恍枰獙?xiě)new jQuery()就能構(gòu)造出來(lái)一個(gè)對(duì)象
- 以前見(jiàn)到的構(gòu)造函數(shù)必須結(jié)合 new 才行
- 結(jié)論
- jQuery 是一個(gè)不需要加 new 的構(gòu)造函數(shù)
- jQuery 不是常規(guī)意義上的構(gòu)造函數(shù)
鏈?zhǔn)斤L(fēng)格
- 查
- jQuery('#xxx')返回并不是一個(gè)元素,而是一個(gè)api對(duì)象
- jQuery('#xxx').find('.red')查找#xxx元素中的.red元素
- jQuery('#xxx').parent()獲取爸爸
- jQuery('#xxx').children()獲取兒子
- jQuery('#xxx').siblings()獲取兄弟
- jQuery('#xxx').index()獲取排行老幾(從0開(kāi)始)
- jQuery('#xxx').next()獲取弟弟
- jQuery('#xxx').prev()獲取哥哥
- jQuery('#xxx').each(fn)遍歷并對(duì)每一個(gè)元素執(zhí)行fn
實(shí)現(xiàn)find
- 這段代碼添加了find方法,但是這個(gè)find方法返回的是數(shù)組,無(wú)法進(jìn)行鏈?zhǔn)讲僮?/li>
- 如果我們將array改成this,其實(shí)在調(diào)用的時(shí)候,返回的是elements
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++){ // 偽數(shù)組要用Array.from() const elements2 = Array.from(elements[i].querySelectorAll(selector)) array = array.concat(elements2) } return this } } } - 我們能不能將elements直接改成array?不可以,一旦修改,會(huì)影響之前我們對(duì)elements的操作
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++){ // 偽數(shù)組要用Array.from() const elements2 = Array.from(elements[i].querySelectorAll(selector)) array = array.concat(elements2) } eleemnts = array return this } } } - 我們需要對(duì)jQuery重新進(jìn)行封裝
- 我們對(duì)于api要重新構(gòu)造,這個(gè)新的api要靠jQuery重新構(gòu)造
- jQuery不能只接收選擇器,還需要接收數(shù)組。
- 如果接收的是數(shù)組就讓elements等于這個(gè)數(shù)組
window.jQuery = function(selectorOrArray){ let elements if(typeof selectorOrArray === 'string'){ elements = document.querySelectorAll(selectorOrArray) }else if(typeof selectorOrArray instanceof 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++){ const elements2 = Array.from(elements[i].querySelectorAll(selectorOrArray)) array = array.concat(elements2) } // 返回一個(gè)新的api return jQuery(array) } } }
實(shí)現(xiàn)end函數(shù)
- 就是回到上一級(jí)的elements
- 我們將之前的api記錄下來(lái)
window.jQuery = function(selectorOrArray){ let elements if(typeof selectorOrArray === 'string'){ elements = document.querySelectorAll(selectorOrArray) }else if(typeof selectorOrArray instanceof Array){ elements = selectorOrArray } return { // 直接將oldApi記錄下來(lái) 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(selectorOrArray)) array = array.concat(elements2) } // 將當(dāng)前操作的api放入數(shù)組中,作為記錄保存下來(lái) array.oldApi = this return jQuery(array) }, end(){ // 直接return就好 return this.oldApi } } } ```
實(shí)現(xiàn)其他的一些操作
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(typeof selectorOrArrayOrTemplate instanceof Array){
elements = selectorOrArrayOrTemplate
}
function createElement(string){
const container = document.createElement("template");
container.innerHTML = string.trim();
return container.content.firstChild;
}
return {
// 標(biāo)記
jquery: true,
elements: elements,
get(index){
return elements[index]
},
appendTo(node){
if(node instanceof Element){
this.each(el => node.appendChild(el))
}else if(node.jquery === true){
this.each(el => node.get(0).appendChild(el))
}
},
append(children){
if(node 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).appendChildren(node))
}
},
find(selector){
let array = []
for(let i=0;i<elements.length;i++){
const elements2 = Array.from(elements[i].querySelectorAll(selectorOrArrayOrTemplate))
array = array.concat(elements2)
}
array.oldApi = this
return jQuery(array)
},
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(parentNode)
}
})
return jQuery(array)
},
children(){
const array = []
this.each((node) => {
if(array.indexOf(node.parentNode) === -1){
array.push(...node.children)
}
})
},
print(){
console.log(elements)
},
addClass(className){
for(let i=0;i<elements.length;i++){
elements[i].classList.add(className)
}
return this
},
oldApi: selectorOrArrayOrTemplate.oldApi,
end(){
return this.oldApi
}
}
}
// 簡(jiǎn)寫(xiě)
window.$ = window.jQuery
命名風(fēng)格
- 下面的代碼令人費(fèi)解
- const div = $('div#test')
- 我們會(huì)誤以為div是一個(gè)DOM
- 實(shí)際上div是jQuery構(gòu)造的api對(duì)象
怎么避免這種誤解呢?
- 改成這樣
- const
('div#text')
- $div.appendChild 不存在,因?yàn)樗皇荄OM對(duì)象
- $div.find存在,因?yàn)樗莏Query對(duì)象
- const
總結(jié)
這是一種什么感覺(jué)
- 就感覺(jué)DOM是不可見(jiàn)的
- 你不需要知道DOM的任何細(xì)節(jié)
- 只需要使用簡(jiǎn)潔的API即可
- 一個(gè)好的封裝,能讓使用者完全不知道內(nèi)部的細(xì)節(jié)
- 這是通過(guò)閉包實(shí)現(xiàn)的