jquery源碼學習之路 ( 二 ) 核心內容

核心模塊

1、對象的構建
// 方法一
function ajquery (name) {
    this.name = name;
    this.sayName = function() {
        return this.name;
    }
    return this;
}

// 方法二
function ajquery(name) {
    this.name = name;
}

ajquery.prototype = {
    sayName : function () {
        return this.name;
    }
}

上面是兩種建立類的方式,雖然最後實現的效果是一致的可是在性能上確實不一致的,當咱們實例化三個ajquery對象的時候,對於方法一,每個實例都有一個sayName方法,這樣就浪費了內存,增長了開銷,方法二則是吧sayName放到了原型鏈上了,這樣每個實例對象都能共享這個方法了(是一個不錯的選擇呢),只是經過scope鏈接到原型鏈上查找,這樣就無形之中也就是多了一層做用域鏈的查找;javascript

jquery的對象在性能上考慮,因此就必須採用原型鏈了呢css

jQuery = function( selector, context ) {
    return new jQuery.fn.init( selector, context );
}
jQuery.fn = jQuery.prototype = {
    init:function(){
        return this
    },
    jquery: version,
    constructor: jQuery,
    // ………………
}

var a = $() ;
2、 jquery中的對象的構造器的分離

new一個實例的過程html

1. 建立一個空對象
2. 將構造函數的做用域賦給這個對象,這個this就指向了這個對象了
3. 執行構造函數中的代碼
4. 返回這個對象

new主要是吧原型鏈跟實例的this鏈接起來java

  • 咱們經常使用的類式寫法以下:
var $$ = ajquery = function (selector) {
    this.selector = selector;
    return this;
}
ajquery.fn = ajquery.prototype = {
    getSelector: function () {
        return this.selector;
    },
    constructor: ajquery
}

var a = new $$('aaa');
a.getSelector();
  • 若是不適用new一樣也能夠實現
var $$ = ajquery = function (selector) {
    if(!(this instanceof ajquery)) {
        return new ajquery(selector);
    }
    this.selector = selector;
    return this;
}
  • jquery中的寫法
var $$ = ajquery = function(selector) {
    return new ajquery.fn.init(selector);
}

ajquery.fn = ajquery.prototype = {
    name: 'meils',
    init: function (selector){
        console.log(this);
    },
    constructor: ajquery
}

init是ajQuery原型上做爲構造器的一個方法,那麼其this就不是ajQuery了,因此this就徹底引用不到ajQuery的原型了,因此這裏經過new把init方法與ajQuery給分離成2個獨立的構造器。jquery

3、 方法的鏈式調用
$('input[type="button"]')
    .eq(0).click(function() {
        alert('點擊我!');
}).end().eq(1)
.click(function() {
    $('input[type="button"]:eq(0)').trigger('click');
}).end().eq(2)
.toggle(function() {
    $('.aa').hide('slow');
}, function() {
    $('.aa').show('slow');
});
// end()方法是將最近的篩選操做還原爲前一步操做的狀態

看這個代碼的結構,咱們或多或少都能猜到其含義:數組

☑ 找出type類型爲button的input元素安全

☑ 找到第一個按鈕,並綁定click事件處理函數app

☑ 返回全部按鈕,再找到第二個jquery插件

☑ 爲第二個按鈕綁定click事件處理函數異步

☑ 爲第三個按鈕綁定toggle事件處理函數

jquery的核心理念是寫的少,辦的多。

jquery.fn.init = function() {
    return this;
}

jquery.fn.get = function() {
    return this;
}

// 經過返回this來實現鏈式操做,由於返回當前實例的this,從而又能夠訪問本身的原型了,這樣的就節省代碼量,提升代碼的效率,代碼看起來更優雅。可是這種方法有一個問題是:全部對象的方法返回的都是對象自己,也就是說沒有返回值,因此這種方法不必定在任何環境下都適合。

雖然Javascript是無阻塞語言,可是他並非沒阻塞,而是不能阻塞,因此他須要經過事件來驅動,異步來完成一些本須要阻塞進程的操做,這樣處理只是同步鏈式,除了同步鏈式還有異步鏈式,異步鏈式jQuery從1.5開始就引入了Promise,jQuery.Deferred後期再討論。

4、插件接口的設計

jQUery爲插件的編寫提供了兩個接口,一種時 $.extend()將其做爲靜態方法處理,另外一種時將方法掛在到 $.fn上,做爲jquery的原型對象的方法。

fn與jQuery實際上是2個不一樣的對象,在以前有講解:jQuery.extend 調用的時候,this是指向jQuery對象的(jQuery是函數,也是對象!),因此這裏擴展在jQuery上。而jQuery.fn.extend 調用的時候,this指向fn對象,jQuery.fn 和jQuery.prototype指向同一對象,擴展fn就是擴展jQuery.prototype原型對象。這裏增長的是原型方法,也就是對象方法了。因此jQuery的API中提供了以上2個擴展函數。

5、插件開發

jquery插件的開發模式一共有三種。

  1. $.extend() 來擴展jquery靜態方法
  2. $.fn 向jquery添加新的實例方法
  3. $.widget()應用jquery ui的部件工廠方式

第一種方式僅僅只是在$的命名空間下建立了一個靜態方法,能夠直接使用$來調用執行便可。而不須要使用$('selector')來選中DOM對象再來調用方法。

第三種方法是用來編寫更高級的插件使用的,通常不使用。

第二種方法是咱們平時開發所,最經常使用的一種形式,稍後咱們將仔細學習該方法

$.extend()
$.extend({
    sayName: function(name) {
        console.log(name);
    }
})

$.sayName('meils');
// 直接使用 $.sayName 來調用

// 經過$.extend()向jQuery添加了一個sayHello函數,而後經過$直接調用。到此你能夠認爲咱們已經完成了一個簡單的jQuery插件了。

這種方法用來定義一些輔助方法,仍是有用的。

$.extend({
    log: function(message) {
        var now = new Date(),
            y = now.getFullYear(),
            m = now.getMonth() + 1, //!JavaScript中月分是從0開始的
            d = now.getDate(),
            h = now.getHours(),
            min = now.getMinutes(),
            s = now.getSeconds(),
            time = y + '/' + m + '/' + d + ' ' + h + ':' + min + ':' + s;
        console.log(time + ' My App: ' + message);
    }
})
$.log('initializing...'); //調用
$.fn 開發插件

1、基本使用

$.fn.setColor = function() {
    this.css('color', 'red');
}

//這裏的this指向調用咱們這個方法的那個對象

2、each()遍歷

咱們也能夠對咱們獲取到的元素的集合的每個元素使用咱們的方法。

$.fn.addUrl = function() {
    this.css('css', 'red');
    this.each(function() {
        $(this).append('i');
    })
}

jquery選擇器選中的是一個集合,所以能夠$.each()來遍歷每個元素,在each內部,this`指的就是每個DOM元素了,若是須要調用jquery的方法,那麼就須要在用$來包裝一下了;

3、鏈式調用

$.fn.setSelector = function() {
    this.css('color', 'red');
    return this.each(function() {

    })
}

4、參數的接收

咱們會使用$.extend()來處理咱們參數,由於這個方法若是傳入多個參數後,他會把這些合併到第一個上。若是存在有同名參數,那麼遵循後面的覆蓋前面的。

// 利用這一點,咱們能夠在插件裏定義一個保存插件參數默認值的對象,同時將接收來的參數對象合併到默認對象上,最後就實現了用戶指定了值的參數使用指定的值,未指定的參數使用插件默認值。

$.fn.setStyle = function(options) {
    var default = {
        color: 'red',
        fontSize: '15px'
    }
    var setting = $.extend(default, optioins);

    return this.css({
        'color': setting.color,
        'fontSize': setting.fontSize
    })
}

上面的參數處理有一點不足,咱們把default也改變了,萬一咱們接下來還要使用該怎麼辦呢,因此咱們還須要在作修改

$.fn.setStyle = function(options) {
    var default = {
        color: 'red',
        fontSize: '15px'
    }
    var setting = $.extend({}, default, optioins);

    return this.css({
        'color': setting.color,
        'fontSize': setting.fontSize
    })
}

5、面向對象開發插件

你可能須要一個方法的時候就去定義一個function,當須要另一個方法的時候,再去隨便定義一個function,一樣,須要一個變量的時候,毫無規則地定義一些散落在代碼各處的變量。

仍是老問題,不方便維護,也不夠清晰。固然,這些問題在代碼規模較小時是體現不出來的。

// 若是將須要的重要變量定義到對象的屬性上,函數變成對象的方法,當咱們須要的時候經過對象來獲取,一來方便管理,二來不會影響外部命名空間,由於全部這些變量名還有方法名都是在對象內部。

var beautify = function(ele, option) {
    this.$element = this.ele;
    this.default = {
        'color': 'red',
        'fontSize': '12px',
        'textDecoration':'none'
    }
    this.ops = $.extend({}, default, option);
}

beautify.prototype = {
    setStyle : function() {
        return this.$element.css({
            'color': this.options.color,
            'fontSize': this.options.fontSize,
            'textDecoration': this.options.textDecoration
        })

        // return this對象,實現鏈式調用

    }
}


$.fn.myPlugin = function(option) {
    var beautify = new beautify(this, option);

    beautify.setStyle();
}



/// 使用

$('a').myPlugin({
    'color': '#2C9929',
    'fontSize': '20px'
});

6、解決命名衝突

由於隨着你代碼的增多,若是有意無心在全局範圍內定義一些變量的話,最後很難維護,也容易跟別人寫的代碼有衝突。
好比你在代碼中向全局window對象添加了一個變量status用於存放狀態,同時頁面中引用了另外一個別人寫的庫,也向全局添加了這樣一個同名變量,最後的結果確定不是你想要的。因此不到萬不得已,通常咱們不會將變量定義成全局的。

一個最好的方法就是始終使用自調用的匿名函數來包裹你的代碼,這樣,就能夠徹底放心的使用本身的變量了。
絕對不會有命名衝突。

;(function() {
    var beautify = function(ele, option) {
    this.$element = this.ele;
    this.default = {
        'color': 'red',
        'fontSize': '12px',
        'textDecoration':'none'
    }
    this.ops = $.extend({}, default, option);
}

beautify.prototype = {
    setStyle : function() {
        return this.$element.css({
            'color': this.options.color,
            'fontSize': this.options.fontSize,
            'textDecoration': this.options.textDecoration
        })

        // return this對象,實現鏈式調用

    }
}


$.fn.myPlugin = function(option) {
    var beautify = new beautify(this, option);

    beautify.setStyle();
}
})();
  • 優化一
var foo=function(){
    //別人的代碼
}//注意這裏沒有用分號結尾

//開始咱們的代碼。。。
(function(){
    //咱們的代碼。。
    alert('Hello!');
})();

因爲前面的代碼沒有加;號 , 而後咱們的插件加載出錯了,因此咱們在咱們插件的最開始加一個;號來強制前面的結束。

  • 優化二

咱們能夠將一些系統變量傳入咱們的插件,這樣能夠縮短變量的做用域鏈,將其做爲咱們插件內部的局部變量來使用,這樣能夠大大提升咱們的速度和性能。

;(function($, window, document, undefined) {
    var beautify = function(ele, option) {
    this.$element = this.ele;
    this.default = {
        'color': 'red',
        'fontSize': '12px',
        'textDecoration':'none'
    }
    this.ops = $.extend({}, default, option);
}

beautify.prototype = {
    setStyle : function() {
        return this.$element.css({
            'color': this.options.color,
            'fontSize': this.options.fontSize,
            'textDecoration': this.options.textDecoration
        })

        // return this對象,實現鏈式調用

    }
}


$.fn.myPlugin = function(option) {
    var beautify = new beautify(this, option);

    beautify.setStyle();
}
})(jQuery, window,document,);

一個安全,結構良好,組織有序的插件編寫完成。


6、版本回溯

jquery能過夠方便的獲取到DOM元素,而且可以遍歷它們。你知道這些背後的原理嗎?

經過對sizzle的分析,咱們知道jquery獲取到的是一個jquery對象,是一個包裝容器。

你會看到在上面有一個prevObject對象。

在jquery內部維護着一個jquery對象棧,遍歷方法每一次遍歷都會找到一個新的元素,每個元素都是一個jquery對象,而後jquery會把這些元素都放到這個棧中。(入棧)

$('ul').find('li');


// 這句話能夠拆分,第一個jquery對象是$('ul'); 第二個jquery對象是$('ul').find('li');


// 首先將$('ul')入棧
// 而後將li集合(類數組對象)入棧

由於棧中的每個元素都是一個jquery對象,每個jquery對象都有着三個屬性,分別時contextselectorprevObject, prevObject 屬性就是指向對象棧中的前一個元素的。

這個prevObject 就比較有意思了。它指向前一個元素。

舉例子了:

<ul id="aaron">
    parent
    <li>child</li>
</ul>

var aaron = $("#aaron");
    aaron.find('li').click(function(){
        alert(1);     //1
    })
// 若是咱們想在返回父級
aaron.find('li').click(function(){
    alert(1);     //1
}).end().click(function() {
    alert(2);
})

// end()方法就是進行了回溯,

jquery爲咱們操縱對象棧提供了兩個有用的方法

  • end()
  • addSelf()

這兩個方法都是進行版本的回退。

  • end()

返回最近的一次篩選的上一個狀態。回到最近的一個"破壞性"操做以前。即,將匹配的元素列表變爲前一次的狀態。

<p><span>Hello</span>,how are you?</p>

$('p').find('span').end();
// 選取全部的p元素,查找並選取span子元素,而後再回過來選取p元素

end()方法返回的就是一個prevObject;

  • addBank
<ul>
   <li>list item 1</li>
   <li>list item 2</li>
   <li class="third-item">list item 3</li>
   <li>list item 4</li>
   <li>list item 5</li>
</ul>

$('li.third-item').nextAll().addBank()
                .css();

// 初始選擇位於第3項,初始化堆棧集合只包含這項。調用.nextAll() 後將第4和第5項推入堆棧。最後,調用.addBack() 合併這兩個組元素在一塊兒,建立一個jQuery對象,指向全部三個項元素(按照文檔中的順序):{[<li.third-item>,<li>,<li> ]}

// [1] li.third-item 入棧
// [2] 四、5入棧
// [3] addBank 合併重組
.addBack()方法致使前一組遍歷堆棧中的DOM元素被添加到當前組。

來舉例子嘍

// First Example
$("div.before-addback").find("p").addClass("background");
// p元素添加背景

// Second Example
$("div.after-addback").find("p").addBack().addClass("background");
// 選中的p元素以及前一個元素合併到棧中,所有添加背景
利用這個DOM元素棧能夠減小重複的查詢和遍歷的操做,而減小重複操做也正是優化jQuery代碼性能的關鍵所在。

在對對象棧的操做中,用到了一個pushStack()

pushStack 生成了一個新 jQuery 對象 ret,ret 的 prevObject 屬性是指向調用 pushStack 函數的那個 jQuery 對象的,這樣就造成了一個棧鏈,其它原型方法如 find、nextAll、filter 等均可以調用 pushStack 函數,返回一個新的 jQuery 對象並維持對象棧。
jQuery.fn.pushStack = function (elems) {

  // 將 elems 合併到 jQuery 對象中
  var ret = jQuery.merge(this.constructor(), elems);

  // 實現對象棧
  ret.prevObject = this;

  // 返回
  return ret;
}
  • end()源碼解析
jQuery.fn.end = function() {
  return this.prevObject || this.constructor; // 若是存在以前的jquery對象就返回它,若是不存在上一個,就返回當前的jQuery對象。
}
  • addBank() 源碼解析
jQuery.fn.addBank = function(selector) {
  return this.add(selector == null ? this.prevObject : this.prevObject.filter(selector));

  // 若是參數爲空,就把當前的jquery對象的上一個jQuery對象一塊兒合併爲一個新的對象。若是指定了參數,那麼就在上一個對象中查找這個對象。

}
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
</ul>

var li = $('ul').find('li');
console.log(li);

//結果以下圖

圖片描述

相關文章
相關標籤/搜索