如果我们希望实现“深复制”,当所复制的对象是数组或者对象时,就应该递归调用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. 否则,直接复制属性。
测试代码如下:
<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中的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; }
这是为了避免无限循环,要复制的属性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]"。