extend()函数是jQuery的基础函数之一,做用是扩展示有的对象javascript
<script type="text/javascript" src="jquery-1.5.2.js"></script> <script> obj1 = { a : 'a', b : 'b' }; obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' }; $.extend(true, obj1, obj2); alert(obj1.x.xxx); // 获得"xxx" obj2.x.xxx = 'zzz'; alert(obj2.x.xxx); // 获得"zzz" alert(obj1.x.xxx); // 得带"xxx" </script> 说明: $.extend(true, obj1, obj2)表示以obj2中的属性扩展对象obj1,第一个参数设为true表示深复制。 虽然obj1中原来没有"x"属性,但通过扩展后,obj1不但具备了"x"属性,并且对obj2中的"x"属性的修改也不会影响到obj1中"x"属性的值,这就是所谓的“深复制”了。
浅复制的实现java
若是仅仅须要实现浅复制,能够采用相似下面的写法: $ = { extend : function(target, options) { for (name in options) { target[name] = options[name]; } return target; } }; 也就是简单地将options中的属性复制到target中。咱们仍然能够用相似的代码进行测试,但获得的结果有所不一样(假设咱们的js命名为“jquery-extend.js”): <script type="text/javascript" src="jquery-extend.js"></script> <script> obj1 = { a : 'a', b : 'b' }; obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' }; $.extend(obj1, obj2); alert(obj1.x.xxx); // 获得"xxx" obj2.x.xxx = 'zzz'; alert(obj2.x.xxx); // 获得"zzz" alert(obj1.x.xxx); // 得带"zzz" </script> obj1中具备了"x"属性,但这个属性是一个对象,对obj2中的"x"的修改也会影响到obj1,这可能会带来难以发现的错误。
深复制的实现 若是咱们但愿实现“深复制”,当所复制的对象是数组或者对象时,就应该递归调用extend。以下代码是“深复制”的简单实现: $ = { extend : function(deep, target, options) { for (name in options) { copy = options[name]; if (deep && copy instanceof Array) { target[name] = $.extend(deep, [], copy); } else if (deep && copy instanceof Object) { target[name] = $.extend(deep, {}, copy); } else { target[name] = options[name]; } } return target; } };
具体分为三种状况:
1. 属性是数组时,则将target[name]初始化为空数组,而后递归调用extend;
2. 属性是对象时,则将target[name]初始化为空对象,而后递归调用extend;
3. 不然,直接复制属性。node
测试代码以下: <script type="text/javascript" src="jquery-extend.js"></script> <script> obj1 = { a : 'a', b : 'b' }; obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' }; $.extend(true, obj1, obj2); alert(obj1.x.xxx); // 获得"xxx" obj2.x.xxx = 'zzz'; alert(obj2.x.xxx); // 获得"zzz" alert(obj1.x.xxx); // 获得"xxx" </script>
如今若是指定为深复制的话,对obj2的修改将不会对obj1产生影响了;不过这个代码还存在一些问题,好比“instanceof Array”在IE5中可能存在不兼容的状况。jQuery中的实现实际上会更复杂一些。
更完整的实现jquery
下面的实现与jQuery中的extend()会更接近一些: $ = function() { var copyIsArray, toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty; class2type = { '[object Boolean]' : 'boolean', '[object Number]' : 'number', '[object String]' : 'string', '[object Function]' : 'function', '[object Array]' : 'array', '[object Date]' : 'date', '[object RegExp]' : 'regExp', '[object Object]' : 'object' }, type = function(obj) { return obj == null ? String(obj) : class2type[toString.call(obj)] || "object"; }, isWindow = function(obj) { return obj && typeof obj === "object" && "setInterval" in obj; }, isArray = Array.isArray || function(obj) { return type(obj) === "array"; }, isPlainObject = function(obj) { if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) { return false; } if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { return false; } var key; for (key in obj) { } return key === undefined || hasOwn.call(obj, key); }, extend = function(deep, target, options) { 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] = extend(deep, clone, copy); } else if (copy !== undefined) { target[name] = copy; } } return target; }; return { extend : extend }; }();
首先是 $ = function(){...}();这种写法,能够理解为与下面的写法相似:数组
func = function(){...};
$ = func();浏览器
也就是当即执行函数,并将结果赋给$。这种写法能够利用function来管理做用域,避免局部变量或局部函数影响全局域。另外,咱们只但愿使用者调用$.extend(),而将内部实现的函数隐藏,所以最终返回的对象中只包含extend:函数
return { extend : extend };性能
接下来,咱们看看extend函数与以前的区别,首先是多了这句话:测试
if (target === copy) { continue; }this
这是为了不无限循环,要复制的属性copy与target相同的话,也就是将“本身”复制为“本身的属性”,可能致使不可预料的循环。
而后是判断对象是否为数组的方式:
type = function(obj) {
return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
},
isArray = Array.isArray || function(obj) {
return type(obj) === "array";
}
若是浏览器有内置的Array.isArray 实现,就使用浏览器自身的实现方式,不然将对象转为String,看是否为"[object Array]"。
最后逐句地看看isPlainObject的实现:
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
若是定义了obj.nodeType,表示这是一个DOM元素;这句代码表示如下四种状况不进行深复制:
1. 对象为undefined;
2. 转为String时不是"[object Object]";
3. obj是一个DOM元素;
4. obj是window。
之因此不对DOM元素和window进行深复制,多是由于它们包含的属性太多了;尤为是window对象,全部在全局域声明的变量都会是其属性,更不用说内置的属性了。
接下来是与构造函数相关的测试:
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
若是对象具备构造函数,但却不是自身的属性,说明这个构造函数是经过prototye继承来的,这种状况也不进行深复制。这一点能够结合下面的代码结合进行理解:
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
这几句代码是用于检查对象的属性是否都是自身的,由于遍历对象属性时,会先从自身的属性开始遍历,因此只须要检查最后的属性是不是自身的就能够了。
这说明若是对象是经过prototype方式继承了构造函数或者属性,则不对该对象进行深复制;这可能也是考虑到这类对象可能比较复杂,为了不引入不肯定的因素或者为复制大量属性而花费大量时间而进行的处理,从函数名也能够看出来,进行深复制的只有"PlainObject"。
若是咱们用以下代码进行测试:
<script type="text/javascript" src="jquery-1.5.2.js"></script> <script> function O() { this.yyy = 'yyy'; } function X() { this.xxx = 'xxx'; } X.prototype = new O(); x = new X(); obj1 = { a : 'a', b : 'b' }; obj2 = { x : x }; $.extend(true, obj1, obj2); alert(obj1.x.yyy); // 获得"xxx" obj2.x.yyy = 'zzz'; alert(obj1.x.yyy); // 获得"zzz" </script>
能够看到,这种状况是不进行深复制的。
总之,jQuery中的extend()的实现方式,考虑了兼容浏览器的兼容,避免性能太低,和避免引入不可预料的错误等因素。
详细出处参考:http://www.jb51.net/article/39288.htm