jquery 源碼解析

靜態與實力方法共享設計css

遍歷方法jquery

$(".a").each()  //做爲實例方法存在算法

$.each()  //做爲靜態方法存在設計模式

Jquery源碼數組

jQuery.prototype = {app

  each:function(callback,args){框架

    return jQuery.each(this,callback,args);ide

  }函數

}性能

實例方法取與靜態方法,換句話來講靜態與實例方法共享設計,靜態方法掛在jquery構造器上面

方法鏈式調用的實現

$(「li」).eq(0).click(function(){

}).end().eq(1).click(function(){

}).end().eq(2).toggle(function(){

  $().hide();  

},function(){

  $().show();

});

其含義:

找出節點,找到第一個li,並綁定click事件處理函數

返回全部li,在找到第二個

爲第二個綁定click事件處理函數

爲第三個li綁定click事件處理函數

 

DSL鏈式代碼的好處

節約js代碼

所返回的都是同一個對象,能夠提升代碼的效率

插件接口的設計

基於插件藉口設計的好處是不少的,最重要的好處是把擴展的功能從主體框架中剝離出去,下降了框架的複雜度,接口的設計比如電腦上的配件,好比CPU,內存,硬盤都是做爲獨立的模塊分離出去了

jquery的插件開發分爲兩種:

一種是掛在jquery命名空間下的全局函數,也能夠稱爲靜態方法

還有一種是jquery對象級別的方法,就是掛在jquery原型下的方法,這樣經過選擇器獲取的jquery對象實例也能共享該方法。

提供的接口

$.extend(target,[object],[objectN])

接口的使用

jquery.extend({

  data:function(){}

  removeData:function(){}

})

jquery.fn.extend({

  data:function(){}

  removeData:function(){}

})

jquery的主題框架就是經過工廠模式返回一個內部的init構造器生成的對象,可是根據通常設計者的習慣,若是要爲jquery添加靜態方法或者實例方法從封裝的角度講師應該提供一個統一的接口才符合設計的。

jquery支持本身擴展屬性,這個對外提供了一個接口,jquery.fn,extend()來對對象增長方法,從jquery的源碼中能夠看出,jquery.extend和jquery.fn.extend實際上是同一方法的不一樣引用。

 

在jquery對象中有一個preobject對象,jquery內部維護這一個jquery對象棧,每一個遍歷方法都會找到一組新元素,而後jquery會把這組元素推入棧中。

每一個jquery對象都有三個屬性:context,selector和prevObejct,其中的precobject屬性就指向這個對象棧中的前一個對象,而經過這個屬性能夠回溯到最初的DOM元素集中。

$("ul").find("li").click(function(){

  alert(1);

}).end().click(function(){

  alert(2);

});

jquery爲咱們操做這個內部對象棧提供了2個方法:end(),addBack()源碼是這樣的:

jQuery.fn.andSelf = jQuery.fn.addBack;

調用第一個方法只是簡單的彈出一個對象(結果就是回到前一個jquery對象),第二個方法更有意思,調用他會在棧中回溯一個位置,而後把兩個位置上的元素集組合起來,並把這個新的,組合以後的元素集推入棧的上方。

end與addBack

end方法主要用於jquery的鏈式屬性中,當沒有使用鏈式用法時,咱們一般只是調用變量名上的前一個對象,因此咱們不須要操做棧。

在使用end()時,咱們能夠一次性調用全部須要的方法 十九回溯到上一個DOM合集,所以對於鏈式操做與優化,這個仍是頗有意義的。

precobject在構建jquery的時候,經過pushStack方法構建

源碼流程解析:

首先建立一個新的jQuery對象,由於construtor是指向構造器的,因此這裏就等於調用了jquery方法了,返回一個新的jquery對象。

 

仿棧與隊列的操做

jQuery既然是模仿的數組結構,那麼確定會實現一套類數組的處理方法,好比常見的棧與隊列操做push、pop、shift、unshift、求和、遍歷循環each、排序及篩選等一系的擴展方法。

jQuery對象棧是一個便於Dom的查找,提供的一系列方法,jQuery能夠是集合元素,那麼咱們怎麼快速的找到集合中對應的目標元素呢?

jQuery提供了.get()、:index()、 :lt()、:gt()、:even()及 :odd()這類索引值相關的選擇器,他們的做用能夠過濾他們前面的匹配表達式的集合元素,篩選的依據就是這個元素在原先匹配集合中的順序。

咱們來分別看一下這幾個選擇器的實現原理:

get方法--是經過檢索匹配jQuery對象獲得對應的DOM元素,以下代碼實現:

get: function(num) {
    return num != null ?
    // Return just the one element from the set
    (num < 0 ? this[num + this.length] : this[num]) :
    // Return all the elements in a clean array
    slice.call(this);
}

原理很簡單,由於jQuery查詢出來的是一個數組的DOM集合,因此就能夠按照數組的方法經過下標的索引取值,固然若是num的值超出範圍,好比小於元素數量的負數或等於或大於元素的數量的數,那麼它將返回undefined。 假設咱們頁面上有一個簡單的無序列表,以下代碼:

<ul>
  <li id="foo">foo</li>
  <li id="bar">bar</li>
</ul>

若是指定了index參數,.get()則會獲取單個元素,以下代碼:

console.log( $( "li" ).get( 0 ) );

因爲索引 index 是以 0 開始計數的,因此上面代碼返回了第一個列表項<li id="foo">foo</li>

然而,這種語法缺乏某些 .get() 所具備的附加功能,好比能夠指定索引值爲負值:

console.log( $( "li" ).get(-1) );

負的索引值表示從匹配的集合中從末尾開始倒數,因此上面這個例子將會返回列表中最後一項:<li id="bar">bar</li>

因爲是數組的關係,因此咱們有幾個快速方法,好比頭跟尾的取值:

first: function() {
    return this.eq( 0 );
},

last: function() {
    return this.eq(-1);
},

get與eq的區別

.eq()  減小匹配元素的集合,根據index索引值,精確指定索引對象。
.get() 經過檢索匹配jQuery對象獲得對應的DOM元素。

一樣是返回元素,那麼eq與get有什麼區別呢?

eq返回的是一個jQuery對象,get返回的是一個DOM對象。舉個例子:

$( "li" ).get( 0 ).css("color", "red"); //錯誤
$( "li" ).eq( 0 ).css("color", "red"); //正確

get方法本質上是把jQuery對象轉換成DOM對象,可是css屬於jQuery構造器的,DOM是不存在這個方法的,若是須要用jQuery的方法,咱們必須這樣寫:

var li = $( "li" ).get( 0 );
$( li ).css("color", "red"); //用$包裝

取出DOM對象li,而後用$再次包裝,使之轉變成jQuery對象,才能調用css方法,這樣要分2步寫太麻煩了,因此jQuery給咱們提供了一個便捷方法eq()。

eq()的實現原理就是在上面代碼中的把eq方法內部轉成jQuery對象:

eq: function( i ) {
    var len = this.length,
        j = +i + ( i < 0 ? len : 0 );
    return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );

上面實現代碼的邏輯就是跟get是同樣的,區別就是經過了pushStack產生了一個新的jQuery對象。

jQuery的考慮很周到,經過eq方法只能產生一個新的對象,可是若是須要的是一個合集對象要怎麼處理?所以jQuery便提供了一個slice方法:

語法:

.slice( start [, end ] )

做用:

根據指定的下標範圍,過濾匹配的元素集合,並生成一個新的 jQuery 對象。

由於是數組對象,意味着咱們能夠用silce來直接取值了,因此針對合集對象咱們能夠這樣寫代碼:

var arr = []
arr.push( this.slice(start[,end]) )     
this.pushStack(arr)

這個this指的是jQuery對象,由於jQuery對象是數組集合,因此咱們能夠經過原生的silce方法直接取到集合數,而後經過包裝處理便可了。

slice: function() {
    return this.pushStack( slice.apply( this, arguments ) );
},

迭代器

迭代器是一個框架的重要設計。咱們常常須要提供一種方法順序用來處理聚合對象中各個元素,而又不暴露該對象的內部,這也是設計模式中的迭代器模式(Iterator)。

jQuery中的$.each方法就是一個典型的迭代器,經過each咱們能夠傳入額外的function,而後來對全部的item項進行迭代操做,以下代碼:

$.each([52, 97], function(index, value) {
  alert(index + ': ' + value);
});
$( "li" ).each(function( index ) {
  console.log( index + ": "" + $(this).text() );
});

針對迭代器,這裏有幾個特色:

☑ 訪問一個聚合對象的內容而無需暴露它的內部。

☑ 爲遍歷不一樣的集合結構提供一個統一的接口,從而支持一樣的算法在不一樣的集合結構上進行操做。

☑ 遍歷的同時更改迭代器所在的集合結構可能會致使問題。

簡單的說:封裝實現,而後迭代器的聚合對象不用關心迭代的過程,從而符合SRP原則。

拋開jQuery的each方法,咱們本身實現一個有簡單的迭代器功能的代碼:

一、簡單回調

function each(obj, callback) {
    var i = 0;
    var value;
    var length = obj.length;
    for (; i < length; i++) {
        callback(obj[i]);
    }
}
var arr = ['a', 'b', 'c'];
each(arr, function(name) {
    console.log(name); 
})

這樣就知足了迭代模式的設計原則,對於集合內部結果經常變化各異,咱們不想暴露其內部結構,但又想讓客戶代碼透明地訪問其中的元素,經過回調把邏輯給解耦出來。可是這樣的處理其實太簡單了,咱們還要考慮至少四種狀況:

☑ 聚合對象,多是對象,字符串或者數組等類型

☑ 支持參數傳遞

☑ 支持上下文的傳遞

☑ 支持循環中退出

咱們簡單的修改一下上面的代碼:

function each(obj, callback, context, arg) {
    var i = 0;
    var value;
    var length = obj.length;
    for (; i < length; i++) {
        callback.call(context || null, obj[i], arg);
    }
}
var arr = ['a', 'b', 'c'];
each(arr, function(name, arg) {
    console.log(name, arg ,this);
}, this, 'aaa')

固然根據回調的處理,從而判斷是否要馬上中斷這個循環,從而節約性能,也是很簡單的,咱們能夠經過獲取處理的返回值來處理,以下代碼:

function each(obj, callback, context, arg) {
    var i = 0;
    var value;
    var length = obj.length;
    for (; i < length; i++) {
        value = callback.call(context || null, obj[i], arg);
        if (value === false) {
            break;
        }
    }

可見只要經過回調函數callback返回的ture/false的布爾值結果就能夠來判斷當前是否要強制退出循環。

 

jQuery的each迭代器

jQuery的each方法從使用上就要分2種狀況:

☑ $.each()函數
☑ $(selector).each()

$.each()函數和$(selector).each()是不同的,後者是專門用來遍歷一個jQuery對象的,是爲jQuery內部服務的。

$.each()函數可用於迭代任何集合,不管是「名/值」對象(JavaScript對象)或數組。在迭代數組的狀況下,回調函數每次傳遞一個數組索引和相應的數組值做爲參數。(該值也能夠經過訪問this關鍵字獲得,可是JavaScript始終將this值做爲一個Object,即便它是一個簡單的字符串或數字值。)該方法返回其第一個參數,這是迭代的對象。

jQuery的實例方法最終也是調用的靜態方法,咱們在以前就解釋過jQuery的實例與原型方法共享的設計。

其中each的實例方法以下:

可見內部是直接調用的靜態方法:

each: function(callback, args) {
    return jQuery.each(this, callback, args);
},

jQuery.each靜態方法:

each: function(obj, callback, args) {
    var value,
        i = 0,
        length = obj.length,
        isArray = isArraylike(obj);

    if (args) {
        if (isArray) {
            for (; i < length; i++) {
                value = callback.apply(obj[i], args);

                if (value === false) {
                    break;
                }
            }
        } else {
            for (i in obj) {
                value = callback.apply(obj[i], args);

                if (value === false) {
                    break;
                }
            }
        }

實現原理幾乎一致,只是增長了對於參數的判斷。對象用for in遍歷,數組用for遍歷。

jQuery能夠是多個合集數組DOM,因此在處理的時候常常就針對每個DOM都要單獨處理,因此通常都須要調用this.each 方法,以下代碼:

dequeue: function( type ) {
        return this.each(function() {
            jQuery.dequeue( this, type );
        });
    },

迭代器除了單純的遍歷,在jQuery內部的運用最多的就是接口的抽象合併,相同功能的代碼功能合併處理:

例如一:

jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

例如二:

jQuery.each({
    mouseenter: "mouseover",
    mouseleave: "mouseout",
    pointerenter: "pointerover",
    pointerleave: "pointerout"
}, function( orig, fix ) {
    //處理的代碼
});

能夠看出上面代碼方法,針對相同的功能,節約了大量的代碼空間

相關文章
相關標籤/搜索