jQuery中extend()和fn.extend()方法详解
这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子:
html代码如下:
<!doctypehtml> <html> <head> <title></title> <scriptsrc='jquery-1.7.1.js'></script> </head> <body> <imgsrc=''/> </body> </html>
下面写js里面的用法:
合并两个普通对象
//给两个普通对象合并属性 varobj1={name:'Tom',age:22}; varobj2={name:'Jack',height:180}; console.log($.extend(obj1,obj2)); //Object{name:"Jack",age:22,height:180}
给jQuery对象添加属性或者方法
$.extend({hehe:function(){alert('hehe');}}); $.hehe(); //alert('hehe')
这个用法很重要,是jQuery内部添加实例属性和方法以及原型属性和方法的实现方法也是编写jQuery插件的方法,下面是jQuery1.7.1中使用extend方法扩展自己的方法和属性
jQuery.extend({ noConflict:function(deep){ if(window.$===jQuery){ window.$=_$; } if(deep&&window.jQuery===jQuery){ window.jQuery=_jQuery; } returnjQuery; }, //IstheDOMreadytobeused?Settotrueonceitoccurs. isReady:false, //Acountertotrackhowmanyitemstowaitforbefore //thereadyeventfires.See#6781 readyWait:1, .....
在这个例子中只传入了一个对象参数,那么默认就把this当做待合并修改的对象
给jQuery对象实例添加属性或者方法
//针对jQuery实例扩展合并 console.log($('img').extend({'title':'img'}));//[img,img#img.img,prevObject:jQuery.fn.jQuery.init[1],context:document,selector:"img",title:"img",constructor:function…]
只合并不修改待合并对象
varobj1={name:'Tom',age:22}; varobj2={name:'Jack',height:180}; console.log($.extend(obj1,obj2)); //Object{name:"Jack",age:22,height:180} console.log(obj1); //Object{name:"Jack",age:22,height:180}
默认情况下,待合并对象跟返回结果一样是被修改了的,如果仅仅想得到一个合并后的对象又不想破坏任何一个原来的对象可以使用此方法
varobj1={name:'Tom',age:22}; varobj2={name:'Jack',height:180}; varempty={}; console.log($.extend(empty,obj1,obj2)); //Object{name:"Jack",age:22,height:180} console.log(obj1); //Object{name:"Tom",age:22}
使用则递归合并或者叫深度拷贝
varobj1={name:'Tom',love:{drink:'milk',eat:'bread'}}; varobj2={name:'Jack',love:{drink:'water',sport:'football'}}; console.log(($.extend(false,obj1,obj2)).love); //Object{drink:"water",sport:"football"} console.log(($.extend(true,obj1,obj2)).love); //Object{drink:"water",eat:"bread",sport:"football"}
详细的使用方法可以看参考手册http://www.w3cschool.cc/manual/jquery/
下面来分析下1.7.1源码中是怎么实现的:
jQuery.extend=jQuery.fn.extend=function(){ varoptions,name,src,copy,copyIsArray,clone, target=arguments[0]||{}, i=1, length=arguments.length, deep=false; ... }
首先是定义了一组变量,因为参数个数不确定所以就直接调用arguments对象访问传递的参数
变量options:指向某个源对象。
‰‰变量name:表示某个源对象的某个属性名。
‰‰变量src:表示目标对象的某个属性的原始值。
‰‰变量copy:表示某个源对象的某个属性的值。
‰‰变量copyIsArray:指示变量copy是否是数组。
‰‰变量clone:表示深度复制时原始值的修正值。
‰‰变量target:指向目标对象。
‰‰变量i:表示源对象的起始下标。
‰‰变量length:表示参数的个数,用于修正变量target。
‰‰变量deep:指示是否执行深度复制,默认为false。
为了更好地了解代码实现这里以上面举的一个例子作为演示观察源代码执行情况
varobj1={name:'Tom',love:{drink:'milk',eat:'bread'}}; varobj2={name:'Jack',love:{drink:'water',sport:'football'}}; $.extend(true,obj1,obj2)
源码分析
//Handleadeepcopysituation if(typeoftarget==="boolean"){ deep=target; target=arguments[1]||{}; //skipthebooleanandthetarget i=2; }
判断是不是深度复制,如果第一个参数是布尔值那么就把第一个参数的值给deep,然后把第二个参数作为目标对象,如果第二个参数不存在就赋值为一个空对象,把源对象的下标改为2,在这个例子里面 是走这里的因为第一个参数是ture然后把deep变成了true,target被修正成了第二个参数也即是obj1,源对象的起始下标为2就是从第三个开始作为源对象也就是本例中的obj2
//Handlecasewhentargetisastringorsomething(possibleindeepcopy) if(typeoftarget!=="object"&&!jQuery.isFunction(target)){ target={}; }
这里对target又进一步进行了处理对于非对象和函数的数据类型而言增加自定义属性是无效的比如字符串自能调用自带的方法和属性
//extendjQueryitselfifonlyoneargumentispassed if(length===i){ target=this; --i; }
如果length属性等于i的值那就表示没有目标对象存在,正常情况下length应该是大于i的值的,那么这个时候就把this作为目标对象把i值减一实现length值大于i值(比i大1)
这个就是jQuery给自己扩展属性的方法的实现原理,只要不传入目标对象就可以啦
两种可能的情况:$.extend(obj) 或者 $.extend(false/true,obj);
for(;i<length;i++){ //Onlydealwithnon-null/undefinedvalues if((options=arguments[i])!=null){ //Extendthebaseobject for(nameinoptions){ src=target[name]; copy=options[name]; //Preventnever-endingloop if(target===copy){ continue; } //Recurseifwe'remergingplainobjectsorarrays if(deep&©&&(jQuery.isPlainObject(copy)||(copyIsArray=jQuery.isArray(copy)))){ if(copyIsArray){ copyIsArray=false; clone=src&&jQuery.isArray(src)?src:[]; }else{ clone=src&&jQuery.isPlainObject(src)?src:{}; } //Nevermoveoriginalobjects,clonethem target[name]=jQuery.extend(deep,clone,copy); //Don'tbringinundefinedvalues }elseif(copy!==undefined){ target[name]=copy; } } } }
这个部分就是此方法的核心了,从arguements对象的第i个下标值开始循环操作首先过滤掉源对象是null或者是undefined的情况可以看到其实
源对象不一定真的就是对像,也可以是其他类型的值比如字符串比如这样写:
console.log($.extend({'name':'tom'},'aa')); //Object{0:"a",1:"a",name:"tom"}
是不是感觉很奇怪啊?究竟是怎么实现的呢?下面接着看
过滤完之后开始进行for循环src保存的是目标对象的某个键的值,copy属性保存的源对象的某个键的值,这两个键都是一样的
//Preventnever-endingloop if(target===copy){ continue; }
如果源对象的某个属性值就是目标对象可能会造成死循环导致程序崩溃所以这里做了一个限制让其跳过此次循环例如:
varo={}; o.n1=o; $.extend(true,o,{n2:o}); //抛出异常: //UncaughtRangeError:Maximumcallstacksizeexceeded
但是这样做也会冤枉一些正常的情况比如:
varobj1={a:'a'} varobj2={a:obj1}; console.log($.extend(obj1,obj2));//Object{a:"a"}
这种情况也是满足源对象值等于目标对象的但是结果发现obj1的a的属性值并没有被修改,就是因为执行了continue,下面把源码的这段话注释掉在执行
Object{a:Object}
这个时候就是正常被修改了个人感觉这个地方需要改进;
接着就是一个if判断就是区分是不是进行深度复制的先不看深度复制的先看一般的
target[name]=copy;
很简单就是只要copy有值就直接复制给目标对象,目标对象有的就修改没有就增加,这样就实现了合并啦。
for循环之后在把新的目标对象返回,所以目标对象最后是被修改的,而且结果和返回的结果是一样的。
//Returnthemodifiedobject returntarget; };
下面再来说说深度复制了怎么去处理
首先保证deep是true,copy有值并且是对象或者数组(如果不是对象和数组深度复制也就无从谈起)然后再分数组和对象来处理,先来看数组的情况:
if(copyIsArray){ copyIsArray=false; clone=src&&jQuery.isArray(src)?src:[];
}else{ clone=src&&jQuery.isPlainObject(src)?src:{}; }