Javascript筆記:(實踐篇)從jQuery插件技術說起-深入分析extend方法(中篇)


1.1     對$.extend的理解

  上面的代碼里我編寫jQuery插件使用到了$.extend方法。這里要講講我以前對jQuery插件開發的誤解,這種誤解源自於我對jQuery插件開發理解的膚淺。

  在我前一家公司,有位做前端的同事很喜歡把自己代碼封裝成jQuery插件,他曾經對我說:jQuery插件技術是jQuery最讓人激動人心的技術,關鍵就是使用extend方法,當時我閱讀一些關於jQuery技術的資料,大多一開始都會提到extend方法的使用,可能自己學習的時候不太仔細,認為jQuery插件技術就是使用extend封裝好javascript代碼,但我每次查看jQuery手冊對extend的解釋又很讓我費解,手冊上說來說去extend方法只不過用於復制對象的方法。

  雖然上面我用extend成功寫出了一個jQuery插件,對extend方法理解的疑惑任然沒有破除,因此這里我要從文檔的描述里的內容好好的研究下extend方法到底是咋回事。

  jQuery手冊對jQuery.extend的解釋:

下面我逐句分析jQuery.extend方法的功能。

(1)       用一個或多個其他對象來擴展一個對象,返回被擴展的對象。這句話很精辟,它概括了extend作用的精髓,extend就是太上老君的煉丹爐,我們把各種不同的對象投進這個丹爐里就會產生一個融合這些對象所有功能的超級對象,這就是extend方法的作用,這個可以用數學公式形象的表述就是A+B=AB。s

(2)       如果不指定target,則給jQuery命名空間本身進行擴展。這有助於插件作者為jQuery增加新方法。要理解這句話,就得分析下extend的參數了。在jQuery1.7的中文手冊里把參數分為兩個版本:

  版本V1.0:target(object),[object1(object)],[objectN(object)],(圓括號里的內容是參數的類型),參數注釋如下:

  版本V1.4: [deep(object)],target(object),object1(object),[objectN(object)],參數注釋如下:

這句話似乎有點問題,如果不指定target應該如何理解了?是說extend方法里不傳值嗎?沒有參數傳入何來的給jQuery命名空間進行擴展啊。如果對比在版本V1.0里對參數的解釋,如果target是唯一的參數那么這樣的用法就是擴展jQuery的命名空間了,這個解釋倒合理些,至少在前面我們寫的jQuery插件里使用到這個用法。后面我會把extend的用法一一做測試,看看這句話到底是翻譯錯誤了?還是我的理解上出現了問題。

(3)       如果第一個參數設置為true,則jQuery返回一個深層次的副本,遞歸地復制找到的任何對象。否則的話,副本會與原對象共享結構。從這句話應該我們越來越明白了extend方法的本質了,extend就是一個javascript語言里的拷貝操作,在大多數包含對象概念的語言里,因為對象的名稱存儲的是對象的別名換種說法就是對象的引用及該對象的地址而不是對象本身,所以當對象進行拷貝操作時候就存在淺拷貝和深拷貝的問題。關於淺拷貝和深拷貝我在以前的博文里做過研究,如果還有那位童鞋不太命名二者的區別,可以參看下面的文章,文章鏈接如下:

java筆記:關於復雜數據存儲的問題--基礎篇:數組以及淺拷貝與深拷貝的問題(上)

java筆記:關於復雜數據存儲的問題--基礎篇:數組以及淺拷貝與深拷貝的問題(下)

(4)       未定義的屬性將不會被復制,然而從對象的原型繼承的屬性將會被復制。第一句好理解沒有定義過的對象屬性當然不會被復制了,因為未定義就等於沒有這個屬性,后半句也好理解,extend方法在做復制操作時候會把對象原型(prototype)繼承到的屬性也加以復制。

為了理解$.extend方法我逐句的分析了jQuery手冊里的解釋,仔細回味下,extend這個可以制作jQuery插件的方法原來就是一個做javascript對象拷貝操作的函數,一個對象拷貝復制函數就是插件技術的核心,這一下子還真的讓人難以接受。

鑒於此,我打算在系統講解extend方法前先好好看看在javascript語言里淺拷貝和深拷貝方法到底如何寫成的,懂了這個或許會對我們正確理解extend的原理很有幫助。

1.2     Javascript里的淺拷貝和深拷貝

  Javascript的賦值都是引用傳遞,就是說,在把一個對象賦值給另一個變量時候,那么新變量所指向的還是賦值對象原來的地址,並沒有為新對象在堆區真正的產生一個新的對象,這個就是所謂的淺拷貝深拷貝則是把原來拷貝的對象真正的復制成一個新對象,而新的變量是指向這個新對象的地址。

  下面我們就來看看javascript里的兩種拷貝的寫法:

1.2.1    淺拷貝

代碼如下:

// 淺拷貝測試
var scopyobj = shallowcopy({},orgval);
scopyobj.obj.content = 'New Object Value';//改變scopyobj里面引用對象的值
// 我們會發現scopyobj和orgval里的obj.content的值都發生了改變
console.log('scopyobj.obj.content:' + scopyobj.obj.content);//scopyobj.obj.content:New Object Value
console.log('orgval.obj.content:' + orgval.obj.content);//orgval.obj.content:New Object Value
// 我們操作數組,結果是一樣的
scopyobj.arrs[1].Array02 = 'I am changed';
console.log('scopyobj.arrs[1].Array02:' + scopyobj.arrs[1].Array02);//scopyobj.arrs[1].Array02:I am changed
console.log('orgval.arrs[1].Array02:' + orgval.arrs[1].Array02);//orgval.arrs[1].Array02:I am changed

 

上面的代碼比較清晰了,這里我就不做過多的講解。

1.2.2    深拷貝

  深拷貝就比較復雜了,有個編程經驗的朋友都知道常常被深拷貝糾結的數據類型其實就兩大類:對象和數組,我們很難控制一個函數里傳入的參數的數據類型,那么一個編寫良好的數據類型判斷函數就顯的重要多了,下面就是javascript一種判斷數據類型的方法,代碼如下:

var whatType = Object.prototype.toString;
console.log('whatType:' + whatType.call({'a':12}));//whatType:[object Object]
console.log('whatType:' + whatType.call([1,2,3]));//whatType:[object Array]
console.log('whatType:' + whatType.call(1));//whatType:[object Number]
console.log('whatType:' + whatType.call('123'));//whatType:[object String]
console.log('whatType:' + whatType.call(null));//whatType:[object Null]
console.log('whatType:' + whatType.call(undefined));//whatType:[object Undefined]
console.log('whatType:' + whatType.call(function (){}));//whatType:[object Function]
console.log('whatType:' + whatType.call(false));//whatType:[object Boolean]
console.log('whatType:' + whatType.call(new Date()));//whatType:[object Date]
console.log('whatType:' + whatType.call(/^[a-zA-Z0-9]{6,32}$/));//whatType:[object RegExp]

  深拷貝會將對象內部的對象一一做復制操作,因此深拷貝的操作應該需要遞歸算法,這里我要再介紹一個函數:arguments.callee。callee 屬性是 arguments 對象的一個成員,他表示對函數對象本身的引用,這有利於匿名函數的遞歸或確保函數的封裝性,關於arguments.callee的使用,大家看下面的代碼:

var i = 0;
function calleeDemo(){
    var whatType = Object.prototype.toString;
    i++;
    if (i < 6){
        arguments.callee();
        console.log(arguments.callee);    
        console.log(whatType.call(arguments.callee));//[object Function]    
    }
}
calleeDemo();//打印5個calleeDemo()

大家看到了arguments.callee的類型是Function,而內容就是calleeDemo()。

好了,打通了技術難點我們來看看深拷貝的代碼應該如何書寫了,代碼如下:

// 深拷貝測試
var dorgval = {//測試數據
    num:1,
    str:'This is String',
    obj:{'content':'This is Object'},
    arrs:['Array NO 01',{'Array02':'This is Array NO 02'}]
},
xQuery = {
    'is':function(dobj,dtype){
        var toStr = Object.prototype.toString;
        return (dtype === 'null' && dtype === 'Null' && dtype === 'NULL') || (dtype === 'Undefined' && dtype === 'undefined' && dtype === 'UNDEFINED') || toStr.call(dobj).slice(8,-1) == dtype;    
    },
    'deepcopy':function(des,src){
        for (var index in src){
            var copy = src[index];
            if (des === copy){
                continue;//例如window.window === window,會陷入死循環,父子相互引用的問題    
            }
            if (xQuery.is(copy,'Object')){
                des[index] = arguments.callee(des[index] || {},copy);
            }else if (xQuery.is(copy,'Array')){
                des[index] = arguments.callee(des[index] || [],copy);
            }else{
                des[index] = copy;    
            }
        }
        return des;
    }
};

var dcopyobj = xQuery.deepcopy({},dorgval);
dcopyobj.obj.content = 'Deep New Object Value';//改變dcopyobj里面引用對象的值
// 測試
console.log('dcopyobj.obj.content:' + dcopyobj.obj.content);//dcopyobj.obj.content:Deep New Object Value
console.log('dorgval.obj.content:' + dorgval.obj.content);//dorgval.obj.content:This is Object
// 測試
dcopyobj.arrs[1].Array02 = 'Deep I am changed';
console.log('dcopyobj.arrs[1].Array02:' + dcopyobj.arrs[1].Array02);//dcopyobj.arrs[1].Array02:Deep I am changed
console.log('dorgval.arrs[1].Array02:' + dorgval.arrs[1].Array02);//dorgval.arrs[1].Array02:This is Array NO 02

既然我們自己寫出來了javascript的深拷貝和淺拷貝,那么我們再去研究jQuery里的深淺拷貝操作一定會事半功倍的。

1.3     $.extend用法詳述

下面我借用jQuery手冊里的實例代碼來講解$.extend的用法。

(1) 測試01:參數個數為2,並且參數類型都是object,代碼如下:

<script type="text/javascript">
$(document).ready(function(xj){//為$定義一個別名xj,防止$沖突
    // 測試01:參數個數為2,並且參數都是object類型
    console.log('==================測試01 start');
    var settings = {'validate':false,'limit':5, 'name':"foo"},
        opts = {'validate':true,'name':'bar'};
    console.log(xj.extend(settings,opts));//Object { validate=true, limit=5, name="bar"}
    console.log(settings);//Object { validate=true, limit=5, name="bar"}
    console.log(opts);//Object { validate=true, name="bar"}
    // 上面的復制操作是淺拷貝還是深拷貝
    settings = {'validate':false,'limit':5, 'name':"foo"},
        opts = {'validate':true,'name':'bar'};
    var resobj = xj.extend(settings,opts);
    resobj.name = 'sharp';
    console.log(resobj);//Object { validate=true, limit=5, name="sharp"}
    console.log(settings);//Object { validate=true, limit=5, name="sharp"}
    console.log(opts);//Object { validate=true, name="bar"}    
    console.log('==================測試01 end');
});
</script>

有上面的結果我們似乎覺得extend默認是淺拷貝,默認下extend的復制到底是淺拷貝還是深拷貝,這個需要一個使用deep標記和不使用deep標記的比較過程,后面我將做這樣的測試。下面看我第一個測試實例。

(2)  測試02:多參數,這里我使用4個參數,參數類型都是object:

    // 測試02:多參數,這里我使用4個參數,參數類型都是object
    console.log('==================測試02 start');
    var empty = {},
        defaults = {'validate':false,'limit':5,'name':"foo"},
        secopts = {'validate':true,'name':"bar"},
        thirdopts = {'id':'JQ001','city':'shanghai'};
    var secsets = xj.extend(empty,defaults,secopts,thirdopts);
    console.log(empty);//Object { validate=true, limit=5, name="bar",id="JQ001",city="shanghai"}
    console.log(secsets);//Object { validate=true, limit=5, name="bar",id="JQ001",city="shanghai"}
    console.log(defaults);//Object { validate=false, limit=5, name="foo"}
    console.log(secopts);//Object { validate=true, name="bar"}
    console.log(thirdopts);//Object { id="JQ001", city="shanghai"}
    console.log('==================測試02 end');

(3) 測試03 :淺拷貝測試,參數為3,第一個是是否深淺拷貝的標記,后面兩個是對象,代碼如下:

    // 測試03  淺拷貝測試,參數為3,第一個是是否深淺拷貝的標記,后面兩個是對象
    console.log('==================測試03 start');
    var shallowsets = {'validate':false,'limit':5, 'name':"foo"},
        shallowopts = {'validate':true,'name':'bar'};
    console.log(xj.extend(false,shallowsets,shallowopts));//Object { validate=true, limit=5, name="bar"}
    console.log(shallowsets);//Object { validate=false, limit=5, name="foo"}
    console.log(shallowopts);//Object { validate=true, name="bar"}
    shallowsets = {'validate':false,'limit':5, 'name':"foo"},
    shallowopts = {'validate':true,'name':'bar'};
    var shallowresobj = xj.extend(false,shallowsets,shallowopts);
    shallowresobj.name = 'ok';
    console.log(shallowresobj);//Object { validate=true, limit=5, name="ok"}
    console.log(shallowsets);//Object { validate=false, limit=5, name="foo"}
    console.log(shallowopts);//Object { validate=true, name="bar"}
    
    var deepsets = {'validate':false,'limit':5, 'name':"foo"},
        deepopts = {'validate':true,'name':'bar'};
    console.log(xj.extend(true,deepsets,deepopts));//Object { validate=true, limit=5, name="bar"}
    console.log(deepsets);//Object { validate=true, limit=5, name="bar"}
    console.log(deepopts);//Object { validate=true, name="bar"}
    deepsets = {'validate':false,'limit':5, 'name':"foo"},
    deepopts = {'validate':true,'name':'bar'};
    var deepresobj = xj.extend(true,deepsets,deepopts);
    deepresobj.name = 'okdeep';
    console.log(deepresobj);//Object { validate=true, limit=5, name="okdeep"}
    console.log(deepsets);//Object { validate=true, limit=5, name="okdeep"}
    console.log(deepopts);//Object { validate=true, name="bar"}
    console.log('==================測試03 end');

 

上面的結果讓我疑惑了,當我把deep參數設置為false時候,extend的返回值和target的值不一致,extend方法的返回值是最終拷貝的結果,而target還是原來的值,而且我去更改返回結果的值時候,target沒有被影響。當deep參數為true的結果和我們不設定deep的結果一樣,那么我們可以這么理解了,默認下extend執行的是深拷貝操作,但是這個結論我還不想過早給出,后面我會分析extend方法的源碼,研究完了源碼我再給出自己的結論。

以上的例子都是用對象做參數,數組的結果和對象一樣,所以這里不再寫關於數組的測試代碼,下面我會使用字符串以及數字類型做測試,看看extend返回的結果是咋樣的。

(4)   測試04:參數個數為2,參數類型都是字符串

代碼如下:

    // 測試04:參數個數為2,參數類型都是字符串
    console.log('==================測試04 start');    
    var strsets = 'strsets',stropts = 'opts';
    var strobj = xj.extend(strsets,stropts);
    console.log(strobj);//Object { 0="o", 1="p", 2="t", 3="s"}
    console.log(strsets);//strsets
    console.log(stropts);//opts
    strsets = 'strsets',stropts = 'opts';
    strobj = xj.extend(false,strsets,stropts);
    console.log(strobj);//Object { 0="o", 1="p", 2="t", 3="s"}
    console.log(strsets);//strsets
    console.log(stropts);//opts
    strobj = xj.extend(true,strsets,stropts);
    console.log(strobj);//Object { 0="o", 1="p", 2="t", 3="s"}
    console.log(strsets);//strsets
    console.log(stropts);//opts
console.log('==================測試04 end');    

拷貝的都是字符串,使用extend真的沒啥意義了,這個反過來也說明extend方法只是針對引用類型的數據做拷貝操作。

(5)  測試05:參數個數為2,target是字符串,第二個是object類型,

代碼如下:

    // 測試05:參數個數為2,target是字符串,第二個是object類型
    console.log('==================測試05 start');
    var targetstr = 'sharpxiajun',
        desobj08 = {'validate':false,'limit':5, 'name':"foo"};
    console.log(xj.extend(targetstr,desobj08));//Object { validate=false, limit=5, name="foo"}
    console.log(targetstr);//sharpxiajun    
    targetstr = 'sharpxiajun',
    desobj08 = {'validate':false,'limit':5, 'name':"foo"};
    console.log(xj.extend(false,targetstr,desobj08));//Object { 0="s", 1="h", 2="a", 更多...}
    console.log(targetstr);//sharpxiajun    
    targetstr = 'sharpxiajun',
    desobj08 = {'validate':false,'limit':5, 'name':"foo"};
    console.log(xj.extend(true,targetstr,desobj08));//Object { validate=false, limit=5, name="foo"}
    console.log(targetstr);//sharpxiajun    
    console.log('==================測試05 end');

這里要注意的是當deep設置為false,extend返回的結果不同,原因我現在說不清,看來從表面使用角度還是很難分析extend方法的原理,一定得從源碼角度進行研究了。

最后我將完整的代碼貼出來,便於大家測試使用:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jQuery Copy Study</title>
</head>
<script type="text/javascript" src="js/jquery-1.7.1.js"></script>
<body>
</body>
</html>
<script type="text/javascript">
$(document).ready(function(xj){//為$定義一個別名xj,防止$沖突
    // 測試01:參數個數為2,並且參數都是object類型
    console.log('==================測試01 start');
    var settings = {'validate':false,'limit':5, 'name':"foo"},
        opts = {'validate':true,'name':'bar'};
    console.log(xj.extend(settings,opts));//Object { validate=true, limit=5, name="bar"}
    console.log(settings);//Object { validate=true, limit=5, name="bar"}
    console.log(opts);//Object { validate=true, name="bar"}
    // 上面的復制操作是淺拷貝還是深拷貝
    settings = {'validate':false,'limit':5, 'name':"foo"},
        opts = {'validate':true,'name':'bar'};
    var resobj = xj.extend(settings,opts);
    resobj.name = 'sharp';
    console.log(resobj);//Object { validate=true, limit=5, name="sharp"}
    console.log(settings);//Object { validate=true, limit=5, name="sharp"}
    console.log(opts);//Object { validate=true, name="bar"}    
    console.log('==================測試01 end');
    
    // 測試02:多參數,這里我使用4個參數,參數類型都是object
    console.log('==================測試02 start');
    var empty = {},
        defaults = {'validate':false,'limit':5,'name':"foo"},
        secopts = {'validate':true,'name':"bar"},
        thirdopts = {'id':'JQ001','city':'shanghai'};
    var secsets = xj.extend(empty,defaults,secopts,thirdopts);
    console.log(empty);//Object { validate=true, limit=5, name="bar",id="JQ001",city="shanghai"}
    console.log(secsets);//Object { validate=true, limit=5, name="bar",id="JQ001",city="shanghai"}
    console.log(defaults);//Object { validate=false, limit=5, name="foo"}
    console.log(secopts);//Object { validate=true, name="bar"}
    console.log(thirdopts);//Object { id="JQ001", city="shanghai"}
    console.log('==================測試02 end');

    // 測試03  淺拷貝測試,參數為3,第一個是是否深淺拷貝的標記,后面兩個是對象
    console.log('==================測試03 start');
    var shallowsets = {'validate':false,'limit':5, 'name':"foo"},
        shallowopts = {'validate':true,'name':'bar'};
    console.log(xj.extend(false,shallowsets,shallowopts));//Object { validate=true, limit=5, name="bar"}
    console.log(shallowsets);//Object { validate=false, limit=5, name="foo"}
    console.log(shallowopts);//Object { validate=true, name="bar"}
    shallowsets = {'validate':false,'limit':5, 'name':"foo"},
    shallowopts = {'validate':true,'name':'bar'};
    var shallowresobj = xj.extend(false,shallowsets,shallowopts);
    shallowresobj.name = 'ok';
    console.log(shallowresobj);//Object { validate=true, limit=5, name="ok"}
    console.log(shallowsets);//Object { validate=false, limit=5, name="foo"}
    console.log(shallowopts);//Object { validate=true, name="bar"}
    
    var deepsets = {'validate':false,'limit':5, 'name':"foo"},
        deepopts = {'validate':true,'name':'bar'};
    console.log(xj.extend(true,deepsets,deepopts));//Object { validate=true, limit=5, name="bar"}
    console.log(deepsets);//Object { validate=true, limit=5, name="bar"}
    console.log(deepopts);//Object { validate=true, name="bar"}
    deepsets = {'validate':false,'limit':5, 'name':"foo"},
    deepopts = {'validate':true,'name':'bar'};
    var deepresobj = xj.extend(true,deepsets,deepopts);
    deepresobj.name = 'okdeep';
    console.log(deepresobj);//Object { validate=true, limit=5, name="okdeep"}
    console.log(deepsets);//Object { validate=true, limit=5, name="okdeep"}
    console.log(deepopts);//Object { validate=true, name="bar"}
    console.log('==================測試03 end');
    
    // 測試04:參數個數為2,參數類型都是字符串
    console.log('==================測試04 start');    
    var strsets = 'strsets',stropts = 'opts';
    var strobj = xj.extend(strsets,stropts);
    console.log(strobj);//Object { 0="o", 1="p", 2="t", 3="s"}
    console.log(strsets);//strsets
    console.log(stropts);//opts
    strsets = 'strsets',stropts = 'opts';
    strobj = xj.extend(false,strsets,stropts);
    console.log(strobj);//Object { 0="o", 1="p", 2="t", 3="s"}
    console.log(strsets);//strsets
    console.log(stropts);//opts
    strobj = xj.extend(true,strsets,stropts);
    console.log(strobj);//Object { 0="o", 1="p", 2="t", 3="s"}
    console.log(strsets);//strsets
    console.log(stropts);//opts
    console.log('==================測試04 end');    
    
    // 測試05:參數個數為2,target是字符串,第二個是object類型
    console.log('==================測試05 start');
    var targetstr = 'sharpxiajun',
        desobj08 = {'validate':false,'limit':5, 'name':"foo"};
    console.log(xj.extend(targetstr,desobj08));//Object { validate=false, limit=5, name="foo"}
    console.log(targetstr);//sharpxiajun    
    targetstr = 'sharpxiajun',
    desobj08 = {'validate':false,'limit':5, 'name':"foo"};
    console.log(xj.extend(false,targetstr,desobj08));//Object { 0="s", 1="h", 2="a", 更多...}
    console.log(targetstr);//sharpxiajun    
    targetstr = 'sharpxiajun',
    desobj08 = {'validate':false,'limit':5, 'name':"foo"};
    console.log(xj.extend(true,targetstr,desobj08));//Object { validate=false, limit=5, name="foo"}
    console.log(targetstr);//sharpxiajun    
    console.log('==================測試05 end');        
});
</script>

1.4     復制一個$.extend方法

  學會了jQuery插件技術,我們完全可以把extend方法從源代碼里摳出來,自己為jQuery定義一個功能和extend一模一樣的插件,復制一個$.extend方法就是想運用一下編寫jQuery的技術,這種運用也是非常有意義的,因為制作jQuery插件讓我獲得了一種研究jQuery源代碼的方式,這種方式或許是我真正理解jQuery源代碼的金鑰匙所在。

  下面是我插件的代碼,文件名稱是:jquery.xjcopy.js,代碼如下:

;(function($){
    $.xjcopy = $.fn.xjcopy = function(){
        var options, name, src, copy, copyIsArray, clone,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false;
    
        // 如果第一個參數是布爾值,那么這是用戶在設定是否要進行深淺拷貝
        if ( typeof target === "boolean" ) {
            deep = target;
            target = arguments[1] || {};
            // 如果第一個參數設置的深淺拷貝標記,那么i設為2,下一個參數才是我們要操作的數據
            i = 2;
        }
    
        // 如果傳入的不是對象或者是函數,可能為字符串,那么把target = {}置為空對象
        if ( typeof target !== "object" && !$.isFunction(target) ) {
            target = {};
        }
    
        // 如果傳入的參數只有一個,跳過下面的步驟
        if ( length === i ) {
            target = this;
            --i;
        }    
        
        for ( ; i < length; i++ ) {
            // 只操作對象值非null/undefined的數據
            if ( (options = arguments[i]) != null ) {
                for ( name in options ) {
                    src = target[ name ];
                    copy = options[ name ];
    
                    // 避免死循環,這個和我寫的深拷貝的代碼類似
                    if ( target === copy ) {
                        continue;
                    }
    
                    // 通過遞歸的方式我們把對象和數組類型的數據合並起來
                    if ( deep && copy && ( $.isPlainObject(copy) || (copyIsArray = $.isArray(copy)) ) ) {
                        if ( copyIsArray ) {
                            copyIsArray = false;
                            clone = src && $.isArray(src) ? src : [];
    
                        } else {
                            clone = src && $.isPlainObject(src) ? src : {};
                        }
    
                        // 不去改變原始對象,只是對原始對象做拷貝操作
                        target[ name ] = $.xjcopy( deep, clone, copy );
    
                    } else if ( copy !== undefined ) {
                        target[ name ] = copy;
                    }
                }
            }
        }
        // 返回結果
        return target;    
    };
})(jQuery)

測試頁面xjqcopy.html代碼如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>用jQuery插件的方式重新定義一個jQuery.extend以及jQuery.fn.extend函數</title>
</head>
<script type="text/javascript" src="js/jquery-1.7.1.js"></script>
<script type="text/javascript" src="js/jquery.xjcopy.js"></script>
<body>
</body>
</html>
<script type="text/javascript">
$(document).ready(function(){
    // 測試01:參數個數為2,並且參數都是object類型
    console.log('==================測試01 start');
    var settings = {'validate':false,'limit':5, 'name':"foo"},
        opts = {'validate':true,'name':'bar'};
    console.log($.xjcopy(settings,opts));//Object { validate=true, limit=5, name="bar"}
    console.log(settings);//Object { validate=true, limit=5, name="bar"}
    console.log(opts);//Object { validate=true, name="bar"}
    // 上面的復制操作是淺拷貝還是深拷貝
    settings = {'validate':false,'limit':5, 'name':"foo"},
        opts = {'validate':true,'name':'bar'};
    var resobj = $.xjcopy(settings,opts);
    resobj.name = 'sharp';
    console.log(resobj);//Object { validate=true, limit=5, name="sharp"}
    console.log(settings);//Object { validate=true, limit=5, name="sharp"}
    console.log(opts);//Object { validate=true, name="bar"}    
    console.log('==================測試01 end');
    
    // 測試02:多參數,這里我使用4個參數,參數類型都是object
    console.log('==================測試02 start');
    var empty = {},
        defaults = {'validate':false,'limit':5,'name':"foo"},
        secopts = {'validate':true,'name':"bar"},
        thirdopts = {'id':'JQ001','city':'shanghai'};
    var secsets = $.xjcopy(empty,defaults,secopts,thirdopts);
    console.log(empty);//Object { validate=true, limit=5, name="bar",id="JQ001",city="shanghai"}
    console.log(secsets);//Object { validate=true, limit=5, name="bar",id="JQ001",city="shanghai"}
    console.log(defaults);//Object { validate=false, limit=5, name="foo"}
    console.log(secopts);//Object { validate=true, name="bar"}
    console.log(thirdopts);//Object { id="JQ001", city="shanghai"}
    console.log('==================測試02 end');

    // 測試03  淺拷貝測試,參數為3,第一個是是否深淺拷貝的標記,后面兩個是對象
    console.log('==================測試03 start');
    var shallowsets = {'validate':false,'limit':5, 'name':"foo"},
        shallowopts = {'validate':true,'name':'bar'};
    console.log($.xjcopy(false,shallowsets,shallowopts));//Object { validate=true, limit=5, name="bar"}
    console.log(shallowsets);//Object { validate=false, limit=5, name="foo"}
    console.log(shallowopts);//Object { validate=true, name="bar"}
    shallowsets = {'validate':false,'limit':5, 'name':"foo"},
    shallowopts = {'validate':true,'name':'bar'};
    var shallowresobj = $.xjcopy(false,shallowsets,shallowopts);
    shallowresobj.name = 'ok';
    console.log(shallowresobj);//Object { validate=true, limit=5, name="ok"}
    console.log(shallowsets);//Object { validate=false, limit=5, name="foo"}
    console.log(shallowopts);//Object { validate=true, name="bar"}
    
    var deepsets = {'validate':false,'limit':5, 'name':"foo"},
        deepopts = {'validate':true,'name':'bar'};
    console.log($.xjcopy(true,deepsets,deepopts));//Object { validate=true, limit=5, name="bar"}
    console.log(deepsets);//Object { validate=true, limit=5, name="bar"}
    console.log(deepopts);//Object { validate=true, name="bar"}
    deepsets = {'validate':false,'limit':5, 'name':"foo"},
    deepopts = {'validate':true,'name':'bar'};
    var deepresobj = $.xjcopy(true,deepsets,deepopts);
    deepresobj.name = 'okdeep';
    console.log(deepresobj);//Object { validate=true, limit=5, name="okdeep"}
    console.log(deepsets);//Object { validate=true, limit=5, name="okdeep"}
    console.log(deepopts);//Object { validate=true, name="bar"}
    console.log('==================測試03 end');
    
    // 測試04:參數個數為2,參數類型都是字符串
    console.log('==================測試04 start');    
    var strsets = 'strsets',stropts = 'opts';
    var strobj = $.xjcopy(strsets,stropts);
    console.log(strobj);//Object { 0="o", 1="p", 2="t", 3="s"}
    console.log(strsets);//strsets
    console.log(stropts);//opts
    strsets = 'strsets',stropts = 'opts';
    strobj = $.xjcopy(false,strsets,stropts);
    console.log(strobj);//Object { 0="o", 1="p", 2="t", 3="s"}
    console.log(strsets);//strsets
    console.log(stropts);//opts
    strobj = $.xjcopy(true,strsets,stropts);
    console.log(strobj);//Object { 0="o", 1="p", 2="t", 3="s"}
    console.log(strsets);//strsets
    console.log(stropts);//opts
    console.log('==================測試04 end');    
    
    // 測試05:參數個數為2,target是字符串,第二個是object類型
    console.log('==================測試05 start');
    var targetstr = 'sharpxiajun',
        desobj08 = {'validate':false,'limit':5, 'name':"foo"};
    console.log($.xjcopy(targetstr,desobj08));//Object { validate=false, limit=5, name="foo"}
    console.log(targetstr);//sharpxiajun    
    targetstr = 'sharpxiajun',
    desobj08 = {'validate':false,'limit':5, 'name':"foo"};
    console.log($.xjcopy(false,targetstr,desobj08));//Object { 0="s", 1="h", 2="a", 更多...}
    console.log(targetstr);//sharpxiajun    
    targetstr = 'sharpxiajun',
    desobj08 = {'validate':false,'limit':5, 'name':"foo"};
    console.log($.xjcopy(true,targetstr,desobj08));//Object { validate=false, limit=5, name="foo"}
    console.log(targetstr);//sharpxiajun    
    console.log('==================測試05 end');    
});
</script>

大家可以看到使用我新封裝的插件,和extend方法執行的結果一模一樣。

下一篇我將分析extend源代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM