jQuery 中的設(shè)計(jì)模式(上)

手寫 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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容