一個後端眼中的jQuery的extend方法

概述

我看的是3.1.0版本的,先上一段代碼吧,不要版本都不同那就尷尬了,這段代碼看着沒有多少,但我相信這基本上是這個世界上執行的最多的代碼了,再不濟也是一個之一。json

直接看代碼有一點繞,因此咱們經過先黑盒的方式,先看實現的功能,在來分析功能實現的代碼是怎麼實現的。後端

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[ 0 ] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    // 若是第一個參數類型是boolean類型,處理深拷貝
    if ( typeof target === "boolean" ) {
        deep = target;

        // 跳過第一個Boolean參數
        target = arguments[ i ] || {};
        i++;
    }

    // 若是target類型不是Object或者function就把目標對象設爲空
    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
        target = {};
    }

    // 若是隻有一個參數就擴展jQuery自己
    if ( i === length ) {
        target = this;
        i--;
    }

    for ( ; i < length; i++ ) {

        // 只處理非空值
        if ( ( options = arguments[ i ] ) != null ) {

            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                // 避免無限循環
                if ( target === copy ) {
                    continue;
                }

                // 遞歸拷貝 plain objects({},new Object()) or arrays(數組)
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {

                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray( src ) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject( src ) ? src : {};
                    }

                    // 不移動原始對象,只是拷貝
                    target[ name ] = jQuery.extend( deep, clone, copy );

                // 不拷貝undefined值
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }
    return target;
};

添加jQuery靜態屬性和實例屬性

這個標題可能有一些迷惑,那咱們可使用另外一個說法,爲jQuery添加屬性和爲jQuery的原型prototype添加屬性(這個屬性多是一個function類型的方法) 。數組

假設咱們都知道jQuery是一個函數對象,因此它是有prototype屬性的,也知道它們有什麼區別,若是不知道能夠先看一下一個後端眼中的js對象和原型鏈這篇文章。框架

js不講道理的地方就在於它太靈活了,以致於不少庫你不看源碼根本不知作了什麼處理,好比jQuery.extend 和 jQuery.fn.extend這個方法,參數個數,參數類型不同處理都不同,而後對於我這樣的後端來講,每一次用就看一下,結果每一次可能都不同,結果接混亂了。函數

這裏順便提一下爲何是這個不是這2個,是由於它們方法體是同一個,js就是這麼神奇。優化

這裏就總結一下:ui

  1. 若是jQuery.extend只有一個參數就是給jQuery添加靜態屬性(通常這個屬性是一個或者一下函數),就是給jQuery函數對象添加屬性。
  2. 若是jQuery.fn.extend只有一個參數就是給jQuery添加實例屬性(通常這個屬性是一個或者一下函數),就是給jQuery函數對象的prototype屬性添加屬性。

下面咱們就來從源碼的角度來分析一下是怎樣實現的: 關鍵就在於下面的代碼:this

if ( i === length ) {
    target = this;
    i--;
}

根據上下文很容易判讀要知足i===length 那麼i===length === 1,接下來就好理解了,只有一個參數的時候target就是this。jQuery.extend調用的時候this是jQuery函數對象,jQuery.fn.extend調用的時候this是jQuery.prototype由於jQuery.fn == jQuery.prototype.net

因此很容易就能得出上面的結論了。prototype

深拷貝與淺拷貝

deep = false;
if ( typeof target === "boolean" ) {
    deep = target;
    target = arguments[ i ] || {};
    i++;
}

從上下文能夠看出默認的是淺拷貝方式,若是第一個參數是一個boolean值,那麼就是指示的是深拷貝或者淺拷貝,第二個參數就變爲了目標對象target。

咱們先來看淺拷貝,拷貝操做的核心代碼就是for循環了,咱們先來看一下淺拷貝,若是隻是淺拷貝的話,咱們能夠把代碼簡化爲下面的樣子:

for ( ; i < length; i++ ) {

        // 只處理非空值
        if ( ( options = arguments[ i ] ) != null ) {

            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                // 避免無限循環
                if ( target === copy ) {
                    continue;
                }if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

這個是否是好理解多了,就是把參數對象中的全部屬性設置到target中,target已經有的就執行的是覆蓋操做。

再來看一下深拷貝的核心代碼:

if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {

    if ( copyIsArray ) {
        copyIsArray = false;
        clone = src && jQuery.isArray( src ) ? src : [];

    } else {
        clone = src && jQuery.isPlainObject( src ) ? src : {};
    }

    // 不移動原始對象,只是拷貝
    target[ name ] = jQuery.extend( deep, clone, copy );
}

這個其實也還好理解,就是對於被拷貝對象copy若是是數組和PlainObject(就是{}和new Object方式生成的對象)就再對這個個copy對象執行一下依次拷貝(遞歸)。

舉個簡單的例子:若是有2個對象:

{ name: "tim", location:{city: "Boston",county:"USA"} }
{ name: "tom",age:20, location: {state: "MA",county:"China"}}
jQuery.extend({ name: "tim", location: { city: "Boston", county: "USA" } }, { name: "tom", age: 20, location: { state: "MA", county: "China" } });

的結果是:

{"name":"tom","location":{"state":"MA","county":"China"},"age":20}
jQuery.extend(true,{ name: "tim", location: { city: "Boston", county: "USA" } }, { name: "tom", age: 20, location: { state: "MA", county: "China" } });

的結果是:

{"name":"tom","location":{"city":"Boston","county":"China","state":"MA"},"age":20}

因此能夠總結一下:淺拷貝只是簡單的執行的是添加或者覆蓋。而深拷貝對於PlainObject或者是數組對象就再遞歸的執行添加拷貝操做,而不是簡單的覆蓋。

jQuery的extend方法的應用

說了這麼多,主要仍是看應用了,我遇到最多的就是插件中了。咱們知道jQuery插件的開發通常是下面這個套路:

;(function ($, window) {
  $.fn.myPlugin = function() {
    //實現代碼
  };
}(jQuery, window));

;分號是避免多個文件壓縮前一個文件中的代碼最後一個語句沒有添加;引發的錯誤。最外層的()小括號是匿名函數自執行。傳入jQuery和window是避免每一次在函數內部使用jQuery和window的時候都要依次查找到最頂層,優化速度。

至於:

$.fn.myPlugin = function() {
    //實現代碼
  };

這個套路,你若是仔細讀了一個後端眼中的js對象和原型鏈這篇文章,應該就能理解,其實本質上就是給jQuery的函數對象的prototype屬性添加方法。

既然說道extend那麼固然除了上面的套路還有extend的方式了:

(function($){
    $.fn.extend({
        f1: function(op){},
        f2:function(op){}
        })
})(jQuery);

國產的ui框架jui就是使用的這個套路。其實本質到同樣,就是給jQuery的函數對象的prototype屬性添加方法。

除了$.fn.extned還有$.extend方法了,好比要擴展String類型的方法就能夠:

$.extend(String.prototype, {
    isPositiveInteger:function(){
        return (new RegExp(/^[1-9]\d*$/).test(this));
    },
    isInteger:function(){
        return (new RegExp(/^\d+$/).test(this));
    }})

其實本質都是同樣的,最重要的是弄清楚js的函數對象,prototype,原型鏈和對象屬性。其餘的基本萬變不離其宗。

相關文章
相關標籤/搜索