本文转自:http://www.xiabingbao.com/jquery/2015/05/30/jquery-extendjquery
原文的排版要比这里美观不少,建议去原文查看。本文仅仅做为我的的mark,方便本身。数组
一般咱们使用jquery的extend时,大都是为了实现默认字段的覆盖,即若传入某个字段的值,则使用传入值,不然使用默认值。以下面的代码:框架
function getOpt(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主要有哪些功能,而后再看实现这些功能的原理。函数
其实从extend的含义里,咱们就能知道extend是作什么的了。extend翻译成汉语后就是:延伸、扩展、推广。oop
咱们来看看$.extend()
提供的参数:jQuery.extend( target [, object1 ] [, objectN ] )
,extend方法须要至少传入一个参数,第一个必需,后面的都是可选参数。若传给extend是两个或两个以上的参数都是对象类型,那么就会把后面全部对象的内容合并给target(第一个对象)上。源码分析
咱们再来看看上面的例子:this
function getOpt(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,只是传入了两个参数,那传的参数再更多一些呢:翻译
function getOpt(target, obj1, obj2, obj3){ $.extend(target, obj1, obj2, obj3); return target; } var _default = { name : 'wenzi', age : '25', sex : 'male' } var obj1 = { name : 'obj1' } var obj2 = { name : 'obj2', age : '36' } var obj3 = { age : '67', sex : {'error':'sorry, I dont\'t kown'} } getOpt(_default, obj1, obj2, obj3); // {name: "obj2", age: "67", sex: {error: "sorry, I dont't kown"}}
这里咱们传入了4个参数,而后getOpt()返回第一个参数的值。从运行的获得结果咱们能够看到,属性值永远是最后一个属性的值。code
还有很重要的一点,$.extend()
实际上是有返回值的,返回的就是修改后的第一个参数的值。如咱们能够把上面的函数修改为这样:
function getOpt(target, obj1, obj2, obj3){ var result = $.extend(target, obj1, obj2, obj3); return result; // // result即修改后的target值 }
若咱们传入的参数不想被修改,咱们能够用一个空对象来做为第一个参数,而后获取$.extend()
的返回值:
function getOpt(target, obj1, obj2, obj3){ var result = $.extend({}, target, obj1, obj2, obj3); return result; // // result即为{}修改后的值 }
刚才咱们在1.1中讲的$.extend()
的例子都是传了两个或两个以上的参数,但其实只有一个参数是必须的。若只传一个参数会怎样呢。
若是只有一个参数提供给
$.extend()
,这意味着目标参数被省略。在这种状况下,jQuery对象自己被默认为目标对象。这样,咱们能够在jQuery的命名空间下添加新的功能。这对于插件开发者但愿向 jQuery 中添加新函数时是颇有用的。
这样咱们就为jQuery扩展了_name
属性和_getName
方法。
针对什么是深度拷贝,什么是浅度拷贝,咱们先来看一个简单的例子。
var obj = {name:'wenzi', sex:'male'}; var obj1 = obj; // 赋值 obj1.name = 'bing'; console.log(obj.name); // bing
咱们修改了obj1中的name值,结果obj中的值也跟着发生了变化,这是为何呢。其实这就是浅度拷贝
:这仅仅是将obj对象的引用地址简单的复制了一份给予变量 obj1,而并非将真正的对象克隆了一份,所以obj和obj1指向的都是同一个地址。当修改obj1的属性或给obj1添加新属性时,obj都会受到影响。
但是若是变量的值不是对象和数组,修改后面的变量是不会影响到前面的变量:
var s = 'hello'; var t = 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, I dont’t kown”}},但是若:
_default.sex.error = 'hello world';
那么obj3.sex.error也会跟着修改,由于obj3.sex是一个object类型。
不过$.extend()
也提供了深度拷贝的方法:jQuery.extend( [deep ], target, object1 [, objectN ] )。若第一个参数是boolean类型,且值是true,那么就会把第二个参数做为目标参数进行合并。
var obj = {name:'wenzi', score:80}; var obj1 = {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了。
其实不看源码,对extend大体的过程应该也是了解的:对后一个参数进行循环,而后把后面参数上全部的字段都给了第一个字段,若第一个参数里有相同的字段,则进行覆盖操做,不然就添加一个新的字段。
下面是jQuery中关于extend的源码,我就在源码上进行注释讲解了,随后再在后面进行总结:
// 为与源码的下标对应上,咱们把第一个参数称为`第0个参数`,依次类推 jQuery.extend = jQuery.fn.extend = function() { var options, 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从当前目标参数的下一个 // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } // 判断目标参数的类型,若目标参数既不是object类型,也不是function类型,则为目标参数从新赋值 // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // 若目标参数后面没有参数了,如$.extend({_name:'wenzi'}), $.extend(true, {_name:'wenzi'}) // 则目标参数即为jQuery自己,而target表示的参数再也不为目标参数 // Extend jQuery itself if only one argument is passed 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是不参与合并的 // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // 使用for~in获取该参数中全部的字段 // Extend the base object for ( name in options ) { src = target[ name ]; // 目标参数中name字段的值 copy = options[ name ]; // 当前参数中name字段的值 // 若参数中字段的值就是目标参数,中止赋值,进行下一个字段的赋值 // 这是为了防止无限的循环嵌套,咱们把这个称为,在下面进行比较详细的讲解 // Prevent never-ending loop if ( target === copy ) { continue; } // 若deep为true,且当前参数中name字段的值存在且为object类型或Array类型,则进行深度赋值 // Recurse if we're merging plain objects or arrays if ( deep && copy && ( 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 // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // deep为false,则表示浅度拷贝,直接进行赋值 // 若copy是简单的类型且存在值,则直接进行赋值 // Don't bring in undefined values } else if ( copy !== undefined ) { // 若原对象存在name属性,则直接覆盖掉;若不存在,则建立新的属性 target[ name ] = copy; } } } } // 返回修改后的目标参数 // Return the modified object return target; };
源码分析完了,下面咱们来说解下源码中存在的几个难点和重点。
在源码中进行了一下这样的判断:
// Prevent never-ending loop if ( target === copy ) { continue; }
为何要有这样的判断,咱们来看一个简单的例子,若是没有这个判断会怎么样:
var _default = {name : 'wenzi'}; var obj = {name : _default} $.extend(_default, obj); console.log(_default);
输出的_default是什么呢:
_default = {name : _default};
_default是object类型,里面有个字段name,值是_default,而_default是object类型,里面有个字段name,值是_default……,无限的循环下去。因而jQuery中直接不进行操做,跳过这个字段,进行下一个字段的操做。
咱们在前面稍微的讲解了一下,变量值为简单类型(如number, string, boolean)进行赋值时是不会影响上一个变量的值的,所以,若是当前字段的值为Object
或Array
类型,须要对其进行拆分,直到字段的值为简单类型(如number, string, boolean)时才进行赋值操做
上面讲解的全都是$.extend(),根本就没讲$.fn.extend()。但是,你有没有发现一个细节,在这段代码的第一行是怎么写的:
jQuery.extend = jQuery.fn.extend = function(){}
也就是说$.extend()与$.fn.extend()共用的是同一个函数体,全部的操做都是同样的,只不过两个extend使用的对象不一样罢了:$.extend()
是在jQuery上进行操做的;而$.fn.extend()
是在jQuery对象上进行操做的,如$(‘div’).extend().
这就是jQuery中extend的实现,之后若咱们须要用到上面的功能时,除了使用$.extend(),咱们也能够在不引入jQuery框架的状况下,本身写一个简单的extend()来实现上面的功能。