jQuery初探:自制jQuery

Caddo發表於2018-11-28

前言

為什麼要學jQuery呢?因為在學習原生js時候,有許多DOM操作冗長複雜,而jQuery卻可以使之簡單化,所以jQuery也曾被眾多程式設計師喜愛。那麼我們該怎麼去學習jQuery呢?所謂知己知彼百戰百勝,我們可以嘗試自制jQuery式的API來了解其原理,在完成這個API後我們就能非常容易的使用jQuery。


一、封裝函式

<!--html程式碼-->
<body>
  <h1>text</h1>
  <ul>
    <li id="item1">選項1</li>
    <li id="item2">選項2</li>
    <li id="item3">選項3</li>
    </ul>
</body>

讓我們來新增以下需求:

  • 獲取到一個節點的所有兄弟姐妹

以item2為例,為了獲取其所有兄弟姐妹,我們可以使用DOM API 中的ParentNode.children屬性。但這有一個問題,我們找到節點父親後返回的是其所有孩子,為了得到除item2外的所有節點,我們必須建立一個空物件,把節點父親返回的所有孩子組成的偽陣列遍歷後通過條件篩選,將不等於自身節點的其他節點放入此空物件裡。

//javaScript程式碼
function getSiblings(node) {
  var allChlidren = node.parentNode.chlidren
  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
}
//呼叫方法
getSiblings(item2)

以上程式碼中,我們建立的array是一個偽陣列,其並沒有陣列的push方法,那麼怎麼才能把value放進陣列呢?所以使用array[array.length]=allChildren[i]的方法(不能放i,因為條件不符合時,會有i跳過了)


  • 給節點新增多個類名

我們可以建立一個classes偽陣列,通過遍歷後,根據判斷其輸入的屬性值所對應的布林值來決定使用DOM API中的node.classList.add或者remove來一次性增刪該節點的多個類名。

//javaScript程式碼
function addClass(node,classes){
  for(let key in classes){
    var value = classes[key]
    var methodName = value? `add` : `remove` 
    node.classList[methodName](key)
  }
}
// 呼叫addClass()
addClass(item1,{ a:ture , b:false})

二、新增名稱空間

在滿足以上兩點需求後,我們要是想讓別人也來使用我們這兩個函式方法,該怎麼辦呢?解決辦法就是給它新增名稱空間。

//javaScript程式碼
window.mmmdom = {}
mmmdom.getSiblings = function(node) {
  var allChlidren = node.parentNode.chlidren
  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
}
mmmdom.addClass = function(node,classes){
  for(let key in classes){
    var value = classes[key]
    var methodName = value? `add` : `remove` 
    node.classList[methodName](key)
  }
}
// 呼叫方法
mmmdom.getSiblings(item3)
mmmdom.addClass( item3,{a:false , b:true })

給它名稱空間還有一個好處就是可以避免不知不覺的把全域性變數給覆蓋。


三、擴充套件Node原型/無侵入設定

以上我們已經滿足了需求而且還可以讓別人來使用,但是還有一個問題就是上面的函式呼叫方法都是textdom.addClass()這樣的,但是按照我們的習慣,我們更喜歡使用item2.getClass(`red`)這樣的呼叫,接下來我們就來實現這一點吧。

1.擴充套件node介面
我們可以直接在 Node.prototype 上新增這個函式,這樣我們的節點就可以繼承到這個函式方法了。

Node.prototype.getSiblings =function ( ) {
  var allChlidren = this.parentNode.chlidren
  var array = { length:0 }
  for(let i=0;i<allChildren.length;i++){
    if(allChildren[i] !== this){
      array[array.length] =allChildren[i]
      array.length += 1
     }
  }
  return array
}
Node.prototype.addClass = function(classes){
  classes.forEach( 
    ( value ) => this.classList.add( value ) 
  )
}
//呼叫函式:
item3.getSiblings.call(items) 
//等價於item3.getSiblings()
item3.addClass.call( item1,[`text`, `red`, `blue`])
//等價於item3.addClass( [`text`, `red`, `blue`])

特別注意:f.call ( asThis , input1 , input2 )
其中this是call()的第一個引數,即asThis會被當作this,而[ input1 ,input2]會被當作arguments

但是這樣的缺點是,當我們呼叫時,若已宣告過這個函式,那該函式就會被覆蓋了。

2.構造新的介面,即無侵入

//javaScript
window.jQuery =  function(node){
  return{
    getSiblings : function(){
      var allChlidren = this.parentNode.chlidren
      var array = { length:0 }
      for(let i=0;i<allChildren.length;i++){
        if(allChildren[i] !== this){
          array[array.length] =allChildren[i]
          array.length += 1
        }
      }
      return array
    },
    addClass : function(classes){
      classes.forEach( 
        (value) => this.classList.add(value) 
      )
    } 
  }
}
var item3 = jQuery(item3)
//實現呼叫
item3.getSiblings()
item3.addClass([` red `])

我們還可以優化window.$ = jQuery
小tips:當一個物件是由 $或jQuery構造出來的,你就在這個物件前也加上$,表示他是jQuery的物件,這樣就不會錯用成dom api了。即 var $node2 = $(node)。


四、傳入非節點

我們以上所有操作都是針對節點,那麼當傳入引數是非節點時該怎麼處理呢?我們應該先對傳入引數進行判斷:如果傳入的是字串,就用document.querySelectorAll()方法來選擇該節點;如果傳入的是節點,我們也把它放入到偽陣列中,這是因為querySelectorAll()返回的是偽陣列,我們要保持一致,後續才能使用同樣操作把兩個方法新增上去。

window.jQuery =  function( nodeOrSelector ){
    let nodes = {}
    if( typeof nodeOrSelector === `string` ){
        nodes = document.querySelectorAll(nodeOrSelector)
    }else if (nodeOrSelector instanceof Node){
        nodes = {
        0:nodeSelector , 
        length:1
        }
    }
    nodes.addClass = function (classes) {
        classes.forEach((value) => {
            for( let i=0 ; i< nodes.length ; i++ ){
                nodes[i].classList.add(value)
            }
        })
    } 
    return nodes
}

這樣,我們就大致理解了jQuery的基本原理了,當然jQuery的作用不僅僅於此,後續實踐會讓我們瞭解更多的。


五、一道面試題

<div id=x></div>
var div = document.getElementById(`x`);
var $div = $(`#x`);
請說出 div 和 $div 的聯絡和區別。

div 和 $div 的聯絡是:
$(div) 可以將 div 封裝成一個 jQuery 物件,就跟 $div 一樣
$div[0] === div ,$div 的第一項就是 div
div 和 $div 的區別是:
div 的屬性和方法有 childNodes firstChid nodeType 等
$div 的 屬性和方法有 addClass removeClass toggleClass 等

相關文章