從不斷「進化」的源代碼來碰面jQuery

jQuery的本質是什麼?

一個JavaScript庫。
接受一箇舊的節點,返回一個新的對象(哈希),這個對象就是jQuery對象。
jQuery的特色就是它擁有本身的API。
jQuery的源碼調用DOM API,可是jQuery對象沒法使用DOM API。javascript

jQuery的便利之處

jQuery極大地簡化了JS編程,也容易上手和學習。
儘量的避免了你使用原生DOM那些很「爛、複雜」的API。
jQuery的兼容性極好。
可是極多的API和不斷更新的時效性,想要透徹瞭解仍是離不開:
熟能生巧。html

實現jQuery的過程

就是編寫源碼,反覆優化實現需求的過程。java

首先咱們來 封裝函數

這裏只舉一個「添加類」的函數爲例。在實際開發中,咱們要聲明不少的函數來實現方法的需求。
咱們使用簡單的html結構node

<body>
    <div>111</div>
    <div>222</div>
    <div>333</div>
    <ul>
        <li id="item1">select1</li>
        <li id="item2">select2</li>
        <li id="item3">select3</li>
    </ul>
</body>

函數的功能:
爲指定節點(node)添加類。編程

function addClass(node,classes) { //給傳入的元素節點添加一個或多個類
  classes.forEach((value)=>node.classList.add(value))
}

addClass(item3,['a','b']) //id爲"item3"的節點

//<li id="item3" class="a b">select3</li>
給予它們一個命名空間

上面的函數,雖然功能已經初步實現,可是有一個很大的問題:
咱們在處理複雜頁面時,爲了方便使用和記憶,可能會聲明不少名字和DOM API極爲「相似」的函數;那麼就會在不知不覺中,覆蓋掉一些API固有的函數,也可能會覆蓋一些全局變量。設計模式

DOM API將衆多方法放在document對象中(window.document.xxx)。咱們也須要給它們一個命名空間,即將其寫入一個庫中,來暫時性的解決這個問題。也同時直接的告訴開發者:咱們聲明的這些函數,是有內在的關聯性的,由於它們都是在操縱節點。數組

命名空間:名字空間,也稱命名空間、名稱空間等,它表示着一個標識符的可見範圍。一個標識符可在多個名字空間中定義,它在不一樣名字空間中的含義是互不相干的。命名空間就是一種設計模式。(詳見維基百科)函數

好比筆者的 name = 'sgs'學習

window.sgs = {} //聲明一個空的庫

function addClass(){如上}
sgs.addClass = addClass

sgs.addClass(item2,['a','c'])
//實現一樣的效果

這樣咱們就能夠調用sgs庫的方法來達到一樣的目的,而避免了全局變量的覆蓋;同時,這些方法都在咱們概括的同一個庫中。優化

這個問題解決之後,咱們又發現:咱們其實並不想這樣使用一個API。
咱們是否能夠直接的對節點調用一個功能函數:item3.addClass()

如何實現?將node放在「前面」
node.fn(x,y)

咱們來「篡改」Node的原型,給Node的公有屬性添加一個屬性。
把函數中的node所有替換爲this,調用函數時默認把this當作第一個參數傳入進來。

Node.prototype.addClass = function(classes) { 
  classes.forEach((value)=>this.classList.add(value))
}

咱們能夠在Node.prototype中找到咱們本身添加的屬性。
使用方法時:

item3.addClass(['a','b','c'])
//=== item3.addClass.call(item3,['a','b','c'])

可是,咱們這樣操做是在改Node的公有屬性。這樣就有很大的麻煩:
若是衆多開發者都在改公有屬性,那麼既會相互覆蓋,同時公有屬性也失去了自己最爲一個標準的意義。

如何再進一步的改進?

咱們本身聲明一個相似的"Node",名爲:"jQuery"


window.jQuery = function(node){ //傳入node節點
  return {
            //key: value
            
            node.APIOne: function(){}
            node.APITwo: function(){}
            ...
            node.addClass: function(classes){
               classes.forEach((value)=>node.classList.add(value))
            }
         }
}

咱們如今是這樣使用的

var nodex = jQuery(item3) //首先根據節點獲取一個新的對象
nodex.addClass(['a','b']) //使用jQuery提供的新API來操做

首先咱們把參數item3傳給了jQuery,而後jQuery將其存在了函數的node裏面;這樣咱們使用其餘方法時,能夠直接經過操做nodex這個對象來操做節點。

若是你傳入的參數不是一個節點,而是表明CSS選擇器的字符串呢?

當你傳入的不是一個id對應的節點,而是表示一個節點的選擇器:

var nodex = jQuery('#item3')

讓jQuery源碼進行傳參類型的判斷:

window.jQuery = function(nodeOrSelector){
  let node
  if(typeof nodeOrSelector === 'string'){
    node = document.querySelector(nodeOrSelector)
    //若是jQuery發現你輸入了字符串,那麼就回去尋找這個字符串對應的節點
  }else{
    node = nodeOrSelector
  }
  return
  ...
}
若是傳入了多個元素節點

當你傳入一個選擇多個元素節點的選擇器時

var nodex = jQuery('ul>li')
var nodey = jQuery('ul li:nth-child(2)')

將1~n個節點所有放在僞數組中。

window.jQuery = function (nodeOrSelector) {
    //--------------------判斷部分--------------------
    let nodes = {}
    if (typeof nodeOrSelector === 'string') {
        let temp = document.querySelectorAll(nodeOrSelector) //僞數組,但它的原型鏈還覆蓋有不少層
        for (let i = 0; i < temp.length; i++) { //獲取純淨的原型鏈,__proto__===Object.prototype
            nodes[i] = temp[i]
        }
        nodes.length = temp.length
    } else if (nodeOrSelector instanceof Node) { //說明傳入的是一個節點,可是保持一致性也要變成僞數組
        nodes = {
            0: nodeOrSelector,
            length: 1
        }
    }
    
    //--------------------添加類方法--------------------
    nodes.addClass = function () { 
        let array = [] //傳入多個字符串表明類,不用傳入數組,內部將字符串組合爲數組
        for (let k = 0; k < arguments.length; k++){
            array.push(arguments[k])
        }
        array.forEach((value) => {
            for (let i = 0; i < nodes.length; i++) { //nodes並非元素,而是僞數組
                nodes[i].classList.add(value)
            }
        })
    }
    
    //------------------設置節點文本方法------------------
    nodes.setText = function (text) { //設置傳入參數的文本
        for (let i = 0; i < nodes.length; i++) {
            nodes[i].textContent = text
        }
    }
    
    //-------------------------------------------------
    return nodes
}

//** 咱們同時引入$,經過$來聲明jQuery對象:**

window.$ = jQuery

使用jQuery的兩個API:

var $div = $('div')
var $li = $('ul>li')
$div.addClass('active')
$li.setText('We are the same')

至此,能夠看出,jQuery的本質就是一個JavaScript庫。經過一個「進化」的過程來達到一個使用的標準。jQuery的API就是源代碼中的屬性對應的函數,並且jQuery的源碼遠比此複雜和繁瑣,才得以實現如此強大的功能。

咱們能夠容許在不徹底透徹理解的狀況下使用一個原理,就像咱們引入外部的庫同樣。

咱們在此只是形象化的瞭解jQuery實現的基本原理。知根知底,才能夠更透徹的學習和使用jQuery。


Edit By: Eden Sheng
EMail: singlesaulwork@gmail.com

相關文章
相關標籤/搜索