深入理解jquery中extend的实现
Jquery的扩展方法extend是我们在写插件的过程中常用的方法,该方法有一些重载原型,下面来看看详细的介绍吧。
通常我们使用jquery的extend时,大都是为了实现默认字段的覆盖,即若传入某个字段的值,则使用传入值,否则使用默认值。
如下面的代码:
functiongetOpt(option){ var_default={ name:'wenzi', age:'25', sex:'male' } $.extend(_default,option); return_default; } getOpt();//{name:"wenzi",age:"25",sex:"male"} getOpt({name:'bing'});//{name:"bing",age:"25",sex:"male"} getOpt({name:'bing',age:36,sex:'female'});//{name:"bing",age:36,sex:"female"}
那现在我们就得需要知道这个extend具体是怎么实现的了,除了实现上面的功能,还有其他作用么?那肯定是有的啦,否则我也不会问那句话了((⊙﹏⊙)b)。我们先来看看extend主要有哪些功能,然后再看实现这些功能的原理。
1.extend能实现的功能
其实从extend的含义里,我们就能知道extend是做什么的了。extend翻译成汉语后就是:延伸、扩展、推广。
1.1将两个或更多对象的内容合并到第一个对象
我们来看看$.extend()提供的参数:jQuery.extend(target[,object1][,objectN]),extend方法需要至少传入一个参数,第一个必需,后面的都是可选参数。若传给extend是两个或两个以上的参数都是对象类型,那么就会把后面所有对象的内容合并给target(第一个对象)上。
我们再来看看上面的例子:
functiongetOpt(option){ var_default={ name:'wenzi', age:'25', sex:'male' } `$.extend(_default,option);` return_default; }
$.extend()中接收了两个参数_default和option,那么extend方法执行时,就会把option对象上字段的值全给了_default。于是_default上设置的默认值就会被option上的值覆盖。当然,若option上没有这个字段,就不会覆盖_default上字段的值。
上面函数中的extend,只是传入了两个参数,那传的参数再更多一些呢:
functiongetOpt(target,obj1,obj2,obj3){ $.extend(target,obj1,obj2,obj3); returntarget; } var_default={ name:'wenzi', age:'25', sex:'male' } varobj1={ name:'obj1' } varobj2={ name:'obj2', age:'36' } varobj3={ age:'67', sex:{'error':'sorry,Idont\'tkown'} } getOpt(_default,obj1,obj2,obj3);//{name:"obj2",age:"67",sex:{error:"sorry,Idont'tkown"}}
这里我们传入了4个参数,然后getOpt()返回第一个参数的值。从运行的得到结果我们可以看到,属性值永远是最后一个属性的值。
还有很重要的一点,$.extend()其实是有返回值的,返回的就是修改后的第一个参数的值。如我们可以把上面的函数修改成这样:
functiongetOpt(target,obj1,obj2,obj3){ varresult=$.extend(target,obj1,obj2,obj3); returnresult;////result即修改后的target值 }
若我们传入的参数不想被修改,我们可以用一个空对象来作为第一个参数,然后获取$.extend()的返回值:
functiongetOpt(target,obj1,obj2,obj3){ varresult=$.extend({},target,obj1,obj2,obj3); returnresult;////result即为{}修改后的值 }
1.2为JQUERY扩展方法或属性
刚才我们在1.1中讲的$.extend()的例子都是传了两个或两个以上的参数,但其实只有一个参数是必须的。若只传一个参数会怎样呢。
如果只有一个参数提供给$.extend(),这意味着目标参数被省略。在这种情况下,jQuery对象本身被默认为目标对象。这样,我们可以在jQuery的命名空间下添加新的功能。这对于插件开发者希望向jQuery中添加新函数时是很有用的。
$.extend({ _name:'wenzi', _getName:function(){ returnthis._name; } }) $._name;//wenzi $._getName();//wenzi
这样我们就为jQuery扩展了_name属性和_getName方法。
1.3深度拷贝和浅度拷贝
针对什么是深度拷贝,什么是浅度拷贝,我们先来看一个简单的例子。
varobj={name:'wenzi',sex:'male'}; varobj1=obj;//赋值 obj1.name='bing'; console.log(obj.name);//bing
我们修改了obj1中的name值,结果obj中的值也跟着发生了变化,这是为什么呢。其实这就是浅度拷贝:这仅仅是将obj对象的引用地址简单的复制了一份给予变量obj1,而并不是将真正的对象克隆了一份,因此obj和obj1指向的都是同一个地址。当修改obj1的属性或给obj1添加新属性时,obj都会受到影响。
可是如果变量的值不是对象和数组,修改后面的变量是不会影响到前面的变量:
vars='hello'; vart=s; t='world'; console.log(s);//hello
那么深度拷贝就不是拷贝引用地址,而是实实在在的复制一份新对象给新的变量。在上面使用$.extend()中,都是使用的浅度拷贝,因此若后面的参数值是object类型或array类型,修改_default(target)的值,就会影响后面参数的值。
如我们使用getOpt(_default,obj1,obj2,obj3);得到的_default值是{name:“obj2”,age:“67”,sex:{error:“sorry,Idont'tkown”}},可是若:
_default.sex.error='helloworld';
那么obj3.sex.error也会跟着修改,因为obj3.sex是一个object类型。
不过$.extend()也提供了深度拷贝的方法:jQuery.extend([deep],target,object1[,objectN])。若第一个参数是boolean类型,且值是true,那么就会把第二个参数作为目标参数进行合并。
varobj={name:'wenzi',score:80}; varobj1={score:{english:80,math:90}} $.extend(true,obj,obj1); obj.score.english=10; console.log(obj.score.english);//10 console.log(obj1.score.english);//80
执行后我们发现,无论怎么修改obj.score里的值,都不会影响到obj1.score了。
2.jQuery中extend实现原理
其实不看源码,对extend大致的过程应该也是了解的:对后一个参数进行循环,然后把后面参数上所有的字段都给了第一个字段,若第一个参数里有相同的字段,则进行覆盖操作,否则就添加一个新的字段。
下面是jQuery中关于extend的源码,我就在源码上进行注释讲解了,随后再在后面进行总结:
//为与源码的下标对应上,我们把第一个参数称为`第0个参数`,依次类推 jQuery.extend=jQuery.fn.extend=function(){ varoptions,name,src,copy,copyIsArray,clone, target=arguments[0]||{},//默认第0个参数为目标参数 i=1,//i表示从第几个参数凯斯想目标参数进行合并,默认从第1个参数开始向第0个参数进行合并 length=arguments.length, deep=false;//默认为浅度拷贝 //判断第0个参数的类型,若第0个参数是boolean类型,则获取其为true还是false //同时将第1个参数作为目标参数,i从当前目标参数的下一个 //Handleadeepcopysituation if(typeoftarget==="boolean"){ deep=target; //Skipthebooleanandthetarget target=arguments[i]||{}; i++; } //判断目标参数的类型,若目标参数既不是object类型,也不是function类型,则为目标参数重新赋值 //Handlecasewhentargetisastringorsomething(possibleindeepcopy) if(typeoftarget!=="object"&&!jQuery.isFunction(target)){ target={}; } //若目标参数后面没有参数了,如$.extend({_name:'wenzi'}),$.extend(true,{_name:'wenzi'}) //则目标参数即为jQuery本身,而target表示的参数不再为目标参数 //ExtendjQueryitselfifonlyoneargumentispassed if(i===length){ target=this; i--; } //从第i个参数开始 for(;i<length;i++){ //获取第i个参数,且该参数不为null和undefind,在js中null和undefined,如果不区分类型,是相等的,null==undefined为true, //因此可以用null来同时过滤掉null和undefind //比如$.extend(target,{},null);中的第2个参数null是不参与合并的 //Onlydealwithnon-null/undefinedvalues if((options=arguments[i])!=null){ //使用for~in获取该参数中所有的字段 //Extendthebaseobject for(nameinoptions){ src=target[name];//目标参数中name字段的值 copy=options[name];//当前参数中name字段的值 //若参数中字段的值就是目标参数,停止赋值,进行下一个字段的赋值 //这是为了防止无限的循环嵌套,我们把这个称为,在下面进行比较详细的讲解 //Preventnever-endingloop if(target===copy){ continue; } //若deep为true,且当前参数中name字段的值存在且为object类型或Array类型,则进行深度赋值 //Recurseifwe'remergingplainobjectsorarrays if(deep&©&&(jQuery.isPlainObject(copy)||(copyIsArray=jQuery.isArray(copy)))){ //若当前参数中name字段的值为Array类型 //判断目标参数中name字段的值是否存在,若存在则使用原来的,否则进行初始化 if(copyIsArray){ copyIsArray=false; clone=src&&jQuery.isArray(src)?src:[]; }else{ //若原对象存在,则直接进行使用,而不是创建 clone=src&&jQuery.isPlainObject(src)?src:{}; } //递归处理,此处为2.2 //Nevermoveoriginalobjects,clonethem target[name]=jQuery.extend(deep,clone,copy); //deep为false,则表示浅度拷贝,直接进行赋值 //若copy是简单的类型且存在值,则直接进行赋值 //Don'tbringinundefinedvalues }elseif(copy!==undefined){ //若原对象存在name属性,则直接覆盖掉;若不存在,则创建新的属性 target[name]=copy; } } } } //返回修改后的目标参数 //Returnthemodifiedobject returntarget; };
源码分析完了,下面我们来讲解下源码中存在的几个难点和重点。
2.1若参数中字段的值就是目标参数,停止赋值
在源码中进行了一下这样的判断:
//Preventnever-endingloop if(target===copy){ continue; }
为什么要有这样的判断,我们来看一个简单的例子,如果没有这个判断会怎么样:
var_default={name:'wenzi'}; varobj={name:_default} $.extend(_default,obj); console.log(_default);
输出的_default是什么呢:
_default={name:_default};
_default是object类型,里面有个字段name,值是_default,而_default是object类型,里面有个字段name,值是_default……,无限的循环下去。于是jQuery中直接不进行操作,跳过这个字段,进行下一个字段的操作。
2.2深度拷贝时进行递归处理
我们在前面稍微的讲解了一下,变量值为简单类型(如number,string,boolean)进行赋值时是不会影响上一个变量的值的,因此,如果当前字段的值为Object或Array类型,需要对其进行拆分,直到字段的值为简单类型(如number,string,boolean)时才进行赋值操作。
3.$.extend()与$.fn.extend()
上面讲解的全都是$.extend(),根本就没讲$.fn.extend()。可是,你有没有发现一个细节,在这段代码的第一行是怎么写的:
jQuery.extend=jQuery.fn.extend=function(){}
也就是说$.extend()与$.fn.extend()共用的是同一个函数体,所有的操作都是一样的,只不过两个extend使用的对象不同罢了:$.extend()是在jQuery($)上进行操作的;而$.fn.extend()是在jQuery对象上进行操作的,如$(‘div').extend()
4.总结
这就是jQuery中extend的实现,以后若我们需要用到上面的功能时,除了使用$.extend(),我们也可以在不引入jQuery框架的情况下,自己写一个简单的extend()来实现上面的功能。
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。