阅读本文前,建议先弄明白JS变量中基本类型和引用类型的区别,这里可以参考我的另一篇博客:JavaScript变量和内存(参数按值传递解惑)。
浅拷贝
对于对象的遍历,我们用for…in语句,将一个对象的所有属性方法都拷贝赋值到另一个对象。如果,被拷贝的对象所有的属性都不是引用类型,那么也不会出现什么问题,可是如果被拷贝的对象某些属性是引用类型的,比如是个Object类型或者Array类型的,就会出问题,因为你赋值过去的只是该引用类型属性的的地址,并不是真的开辟空间进行了深度拷贝,所以,这些引用类型的属性是共享的,改变一个,另一个也改变了。
下面的例子,被拷贝的obj都不是引用类型,那么也就没什么问题。
但是,下面的例子,有的属性比如family和school是引用类型的属性,那么就出现了共享的问题:
深拷贝
深拷贝就是解决浅拷贝共享的问题,如果我们需要独立的拷贝出来的对象,互不影响的,那么我们需要使用深度拷贝来解决这个问题。
考虑到数组和对象的情况,我们需要在遍历的时候对属性的类型进行判断,如果是引用类型,我们就得注意了。
补充:关于callee的用法:
- callee返回正在执行的函数本身的引用,它是arguments的一个属性;
- 这个属性只有在函数执行的时候才有效;
- callee有个属性叫length,可以获得形参的个数,因此可以用来比较形参和实参个数是否一致,即比较arguments.length 是否等于 arguments.callee.length;
- 它可以用来递归匿名函数。
function test() {
alert(arguments.callee);
}
这个用途是递归:
其中函数内部包含了对sum自身的引用,函数名仅仅是个变量名,在函数内部调用sum即相当于调用一个全局变量,不能很好的体现出是调用自身,这时使用callee会是个比较好的方法。
考虑到数组和对象的情况,我们需要对传入的数据类型进行判断,通过如下的方法:
这里用到了arguments.callee()方法,它表示的是在哪个函数中执行,就代表哪个函数。在如下的代码中实际指向的是deepClone函数,也就是另一种递归调用的方式。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>LazyLoad</title>
<style type="text/css">
</style>
</head>
<body>
<script type="text/javascript">
var obj = {
name: 'guoyu',
age: 28,
sex: 'male',
family: ['father','mather','sister'],
school: {
primary: 'ZGXX',
middle: 'XYYZ',
high: 'HZKJDX'
},
fun: function() {alert('hello');}
};
function isClass(o) {
if (o == null) {return 'Null';}
if (o == undefined) {return 'Undefined';}
//toString返回的:'[object Array]',slice(8,-1)就是Array,返回类型
return Object.prototype.toString.call(o).slice(8, -1);
}
function deepClone(obj) {
var result;
var oClass = isClass(obj);
if (oClass === 'Object') {
result = {};
} else if (oClass === 'Array') {
result = [];
} else {
return obj;
}
for (key in obj) {
var copy = obj[key];
if (isClass(copy) === 'Object') {
//递归调用
result[key] = arguments.callee(copy);
alert(arguments.callee);//指的是deepClone这个函数本身
} else if (isClass(copy) === 'Array') {
result[key] = arguments.callee(copy);
} else {
result[key] = obj[key];
}
}
return result;
}
var ret = deepClone(obj);
</script>
</body>
</html>