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的解释:

Javascript笔记:(实践篇)从jQuery插件技术说起-深入分析extend方法(中篇)_第1张图片

下面我逐句分析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)],(圆括号里的内容是参数的类型),参数注释如下:

Javascript笔记:(实践篇)从jQuery插件技术说起-深入分析extend方法(中篇)_第2张图片

  版本V1.4: [deep(object)],target(object),object1(object),[objectN(object)],参数注释如下:

Javascript笔记:(实践篇)从jQuery插件技术说起-深入分析extend方法(中篇)_第3张图片

这句话似乎有点问题,如果不指定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源代码。

你可能感兴趣的:(JavaScript)