JavaScript对象克隆

前言

之前有人问我如何克隆一个JS对象,我当时没答上来;过后我查资料弄懂了这个问题,现在整理成文。

正文

JavaScript的一切实例都是对象,但他们也分为原始类型和引用类型。原始类型对象指的是字符串(String)、数值(Number)、布尔值(Boolean)、undefinednull,引用类型对象指的是数组(Array)、对象(Object)、函数(Function)。
既然对象分为这两类,他们的复制克隆也是有差别的。普通对象存储的是对象的实际数据,而引用对象存储的是对象的引用地址,而把对象的实际内容单独存放。下面我们就来看看他们克隆的区别。

原始类型对象的克隆

字符串的克隆

1
2
3
4
5
6
var x="abc";
var y=x;
y="xyz";
alert(x); // "abc"
alert(y); // "xyz"

数值的克隆

1
2
3
4
5
6
var x=1;
var y=x;
y=2;
alert(x); // 1
alert(y); // 2

布尔值的克隆

1
2
3
4
5
6
var x=true;
var y=x;
y=false;
alert(x); // true
alert(y); // false

我们可以看出来原始类型的克隆很简单,只需要一个=赋值就可以了;undefinednull也是同理。

引用类型的对象克隆

数值的克隆

如果采用普通克隆:

1
2
3
4
5
6
var x=[1,2];
var y=x;
y.push(3);
alert(x); // 1,2,3
alert(y); // 1,2,3

由上可知,原始数组x,克隆数组y,修改了克隆数组y,但也同时修改了原始数组x,这就是引用对象的特点。那么怎样才能达到完整的数组克隆呢?

1
2
3
4
5
6
7
8
9
10
11
12
var x=[1,2];
var y=[];
var i=0;
var j=x.length;
for(;i<j;i++)
{
y[i]=x[i];
}
y.push(3);
console.log(x); // [1,2]
console.log(y); // [1,2,3]

这样,两个数值就互不干扰,实现了完整数组克隆。

对象的克隆

和数组的克隆同理,对象的完整克隆如下:

1
2
3
4
5
6
7
8
9
10
11
var x={a:2,b:4};
var y={};
var i;
for(i in x)
{
y[i]=x[i];
}
y.c=6;
console.log(x); // Object {a: 2, b: 4}
console.log(y); // Object {a: 2, b: 4, c: 6}

函数的克隆

1
2
3
4
5
6
var x=function(){alert(1);};
var y=x;
y=function(){alert(2);};
console.log(x); // function(){alert(1);};
console.log(y); // function(){alert(2);};

函数的克隆与原始类型对象克隆的方式类似;只需要使用=就可以了。

总结

根据上面的情况,另外,克隆引用对象必须采用完整克隆(深度克隆),包括对象的值也是一个对象也要进行完整克隆(深度克隆)。所以,我们可以总结并封装一个通用的克隆方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function clone(obj){
var o,i,j,k;
if(typeof(obj)!=="object" || obj===null)return obj;
if(obj instanceof Array){
o=[];
i=0;j=obj.length;
for(;i<j;i++){
if(typeof(obj[i])==="object" && obj[i]!=null){
o[i]=clone(obj[i]);
}else{
o[i]=obj[i];
}
}
}else{
o={};
for(i in obj){
if(typeof(obj[i])==="object" && obj[i]!==null){
o[i]=clone(obj[i]);
}else{
o[i]=obj[i];
}
}
}
return o;
}