模擬 jQuery API的實現(xiàn)

jQuery 是什么子眶?

jQuery實質(zhì)上是一個構(gòu)造函數(shù),接受一個參數(shù)架忌,這個參數(shù)可能是節(jié)點,然后返回一個方法對象去操作節(jié)點诺舔。官方文檔是這樣說明的:
jQuery是一個快速,小巧备畦,功能豐富的JavaScript庫低飒。它通過易于使用的API在大量瀏覽器中運行,使得HTML文檔遍歷和操作懂盐,事件處理褥赊,動畫和Ajax變得更加簡單。

那么今天我們就來演示一下jQuery API的工作原理

用原生DOM寫一個類似jQuery的API

1.寫一個帶有id的 ul 列表

<!DOCTYPE html>
<html lang="zh">

    <head>
        <meta charset="UTF-8" />
        <meta name="viewport"
          content="width=device-width, 
          initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>模擬 jQuery API的實現(xiàn)</title>
    </head>

    <body>
        <ul>
            <li id="item1">選項1</li>
            <li id="item2">選項2</li>
            <li id="item3">選項3</li>
            <li id="item4">選項4</li>
            <li id="item5">選項5</li>
            <li id="item6">選項6</li>
        </ul>
        <script>
        </script>
    </body>

</html>



2. 以item3為節(jié)點莉恼,找到其兄弟節(jié)點

通過 var allChildren = item3.parentNode.children 獲取 item3 父節(jié)點的所以子節(jié)點拌喉,然后遍歷所有子節(jié)點,得到 item3以外 的所有節(jié)點俐银,這樣就找到選項3的所以兄弟節(jié)點啦尿背。可以 console.log一下捶惜。
(由于array是偽數(shù)組田藐,不能用push的方法,所以我們用到 array[array.length] = allChildren[i] 的方法)

<script>
      var allChildren = item3.parentNode.children
      var array = {length:0}
      for(let i = 0; i < allChildren.length; i++){
        if(allChildren[i] !== item3){
          array[array.lenth]=allChildren[i]
          array.length += 1
        }
      }
      console.log(array)
</script>



3. 代碼封裝

封裝的好處有很多:給代碼一個名字方便調(diào)用吱七;形成局部變量可以避免覆蓋JS原始變量(立即調(diào)用函數(shù))等
給這個函數(shù)取個名字汽久,如 getSiblings;把 item3 換成 node踊餐,這樣輸入任意節(jié)點都可以使用這個函數(shù)了景醇;注意不要忘記 return,這樣我們就得到了一個函數(shù) function getSiblings(node){}

<script>
      function getSiblings(node){
        var allChildren = node.parentNode.children
        var array = {length:0}
        for(let i = 0; i < allChildren.length; i++){
          if(allChildren[i] !== node){
            array[array.lenth]=allChildren[i]
            array.length += 1
          }
        }
        return array
      }
</script>



4. 封裝函數(shù):function addClass(node, classes){}

現(xiàn)在我們要給 item3 加 class屬性
首先我們聲明一個 classes 對象吝岭,里面有 a三痰、b、c 三個屬性窜管;同時分別給它們一個布爾值酒觅,方便 add 和 remove;遍歷各個屬性微峰。

      var classes = {'a':false, 'b':true, 'c':true}
      for(let key in classes){
        var value = classes[key]
        var methodName = value?'add':'remove';
        item3.classList[methodName](key)
      }

可以看到舷丹,class b、c已經(jīng)被添加到 item3 中了
同樣我們來封裝一下這些代碼蜓肆,如下所示:

function addClass(node, classes){
  for (var key in classes){
    var value = classes[key]
    var methodName = value ? 'add' : 'remove'
    //console.log (methodName )
    //console.log (node.classList)
    //console.log (node.classList.add)
    //console.log (node.classList[methodName])
    node.classList[methodName](key)
  }
}
/*
obj.x()  等同于  obj['x']()
注意一點上述代碼不能用點運算符颜凯,要用[]運算符谋币,classList['add'] === classList.add
*/
addClass(item3, {a:true, b:false, c:true})

現(xiàn)在,只要你給一個 node 和 classes 于此函數(shù)症概,就可以給 該節(jié)點添加 classes所包含的正確屬性



5.命名空間:

給封裝的函數(shù)一個名字蕾额,方便其他人使用,同時防止與前人命名的沖突彼城。
假如我們想把這兩個封裝好好的函數(shù)聯(lián)系到一起诅蝶,以便以后調(diào)用的話,我們可以這樣去寫募壕。

window.xxdom = {} 
xxdom.getSiblings = function (node) { 
  var allChildren = node.parentNode.children

  var array = {
    length: 0
  }
  for (let i = 0; i < allChildren.length; i++) {
    if (allChildren[i] !== node) {
      array[array.length] = allChildren[i]
      array.length += 1
    }
  }
  return array
}
xxdom.addClass = function (node, classes) {
  classes.forEach( (value) => node.classList.add(value) )
}

xxdom.getSiblings(item3)
xxdom.addClass(item3, ['a','b','c'])



6.能不能把node 放在前面

node.getSiblings()
node.addClass()

我們發(fā)現(xiàn)當(dāng)我們要用到上面的命名空間的時候會非常麻煩调炬,我們總是要用 dom.getSiblings()、xxdom.addClass()舱馅、總是要帶有別人的一個小尾巴缰泡。 item3.getSiblings()、item3.addClass() 好像看起來更清爽一些代嗤,有沒有辦法來實現(xiàn)呢棘钞?
其實是有的。為了驗證干毅,我們給 Node 的共有屬性添加 getSiblings屬性宜猜,然后我們測試下能否訪問到:

      Node.prototype.getSiblings = function(){
        return 1
      }

現(xiàn)在有個問題,我們這里的getSiblings()怎么獲取到item3 硝逢?
方法一:擴展 Node 接口
直接在 Node.prototype 上加函數(shù)
Node 如何取到 item3宝恶?
this
why趴捅?把上面寫成 .call 的形式垫毙,因為this 是call 的第一個參數(shù)。


那么 item3 為什么會有 getSiblings屬性呢拱绑?
因為我們篡改了其 proto 最終指向的 node.prototype 的共有屬性综芥,然后添加了一個 getSiblings的方法,然后它里面呢先去獲取this 的父節(jié)點的所有兒子猎拨,那么 this 是誰呢膀藐?this 就是你在調(diào)用的時候就會幫你把 item3給傳遞進來,item3會自動的傳給 getSiblings() 的 this红省,所以 this 就是.前面的東西额各,不管你是什么。然后聲明一個偽數(shù)組吧恃,遍歷這個偽數(shù)組虾啦,如果偽數(shù)組里面的第[i]項全不等于 this (這時候的 this 就是 item3),那么不等于 item3的 item放到偽數(shù)組里面,偽數(shù)組的 length +1傲醉,循環(huán)后返回這個偽數(shù)組,于是我們得到了 item1硬毕、item2呻引、item4吐咳、item5。

** 自己命名新的接口 Node2**
前面寫的內(nèi)容其實不是很好韭脊,為什么呢童谒?
因為我們在改 node 的共用屬性乾蓬。比如 A 同學(xué)在上面添加了2個函數(shù)慎恒,B 同學(xué)也在上面添加了2個函數(shù)任内,然后我們也添加了2個函數(shù),所以就存在被覆蓋的可能性融柬。
那, 如果不改原型死嗦,我們怎么能實現(xiàn) item3.getSiblings呢?方法也是有的粒氧!就是命名新的接口越除,示例如下:

 function Node2(node){
     return {
         element: node,
         getSiblings: function(){

         },
         addClass: function(){

         }
     }
 }
 let node =document.getElementById('x')
 let node2 = Node2(node)
 node2.getSiblings()
 node2.addClass()



真實代碼(這種對 Node 沒有產(chǎn)生破壞的方法叫做「無侵入」)如下:

window.Node2 = function (node) {
  return {
    getSiblings: function () {
      var allChildren = node.parentNode.children
      var array = {
        length: 0
      }
      for (let i = 0; i < allChildren.length; i++) {
        if (allChildren[i] !== node) {
          array[array.length] = allChildren[i]
          array.length += 1
        }
      }
      return array
    },
    addClass: function (classes) {
      for (var key in classes) {
        var value = classes[key]
        var methodName = value ? 'add' : 'remove'
        node.classList[methodName](key)
      }
    }
  }
}



7. 把 Node2 改成jQuery

通過上面的操作,我們就在原有 Node 的基礎(chǔ)上新擴展了接口外盯,我們這時候就可以給新擴展的起個自己喜歡的名字摘盆,就叫jQuery吧

function jQuery(node) {
  return {
    element: node,
    getSiblings: function () {
    },
    addClass: function () {
    }
  }
}
let node = document.getElementById('x')
let node2 = jQuery(node)
node2.getSiblings()
node2.addClass()

實際操作如下:

window.jQuery = function (nodeOrselector) {
  let nodes = {}
  if (typeof nodeOrselector === 'string') {
    let temp = document.querySelectorAll(nodeOrselector) //偽數(shù)組
    for (let i = 0; i < temp.length; i++) {
      nodes[i] = temp[i]
    }
    nodes.length = temp.length
  } else if (nodeOrSelector instanceof node) {
    node = {
      0: nodeOrSelector,
      length: 1
    }
  }

  nodes.getSiblings = function () {
    var allChildren = node.parentNode.children
    var array = {
      length: 0
    }
    for (let i = 0; i < allChildren.length; i++) {
      if (allChildren[i] !== node) {
        array[array.length] = allChildren[i]
        array.length += 1
      }
    }
    return array
  }


  nodes.addClass = function (classes) {
    classes.forEach((value) => {
      for (let i = 0; i < nodes.length; i++) {
        node[i].calssList.add(value)
      }
    })
  }

  return nodes

}

window.$ = jQuery   //縮寫吧:alias
var $div = $('div')
$div.addClass('red') // 可將所有 div 的 class 添加一個 red
$div.setText('hi') // 可將所有 div 的 textContent 變?yōu)?hi



8. $縮寫與alias

window.$ = jQuery
即var node2 = $(node)

但是為了防止記混 node2 到底有沒有引入 jQuery
大家通常這樣寫:

var $node2 = $(node)

我們會在引用的 jQuery標(biāo)簽前面加上$符號來區(qū)分原生的標(biāo)簽或者接口,看到$我們立馬就明白了這是由 jQuery 引用染生出來的饱苟。

到這里我們已經(jīng)知道 jQuery 是個什么了:它就是一個函數(shù)孩擂,是 JS 原始 DOM 的擴展,便于我們更好得使用JS寫代碼的加強版 DOM API箱熬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末类垦,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子城须,更是在濱河造成了極大的恐慌蚤认,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糕伐,死亡現(xiàn)場離奇詭異砰琢,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門氯析,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亏较,“玉大人,你說我怎么就攤上這事掩缓⊙┣椋” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵你辣,是天一觀的道長巡通。 經(jīng)常有香客問我,道長舍哄,這世上最難降的妖魔是什么宴凉? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮表悬,結(jié)果婚禮上弥锄,老公的妹妹穿的比我還像新娘。我一直安慰自己蟆沫,他們只是感情好籽暇,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饭庞,像睡著了一般戒悠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舟山,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天绸狐,我揣著相機與錄音寒矿,去河邊找鬼。 笑死符相,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的主巍。 我是一名探鬼主播挪凑,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼搞旭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肄渗,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翎嫡,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惑申,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年人芽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绩脆。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惕味,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赦拘,我是刑警寧澤慌随,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站丸逸,受9級特大地震影響剃袍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜民效,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望业扒。 院中可真熱鬧,春花似錦程储、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帚呼。三九已至,卻和暖如春萝挤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怜珍。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工凤粗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嫌拣。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像捶索,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子灰瞻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355