前端静径

js对象深拷贝和浅拷贝

浅复制和深复制都可以实现在已有对象的基础上再生一份对象的作用,但是对象的实例存储都是在堆内存中,然后通过一个引用值去操作对象的,所以复制对象的时候就存在两种情况:复制引用和复制实例,这也是浅复制和深复制的区别所在。

  • 浅复制:浅复制是复制引用,复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响

  • 深复制:深复制不是简单的复制引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深复制的对象的引用图不包含任何原有对象或对象图上的任何对象,复制后的对象与原来的对象是完全隔离的

简单来说,浅复制只复制一层对象的属性,而深复制则递归复制了所有层级。

浅拷贝js对象

正如上面说的,浅拷贝只拷贝一层对象的属性,因此实现起来是很方便的,下面是一个简单的自己写浅拷贝的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var sourceObj = {
name: 'jessica',
age: 27,
job: 'web',
friends: ['XuXiaojuan', 'GuJuanjuan']
}
function cloneObj(sourceObj) {
var targetObj = {};
for(var prop in sourceObj) {
if(sourceObj.hasOwnProperty(prop)) {
targetObj[prop] = sourceObj[prop];
}
}
return targetObj;
}
var targetObj = cloneObj(sourceObj);

但是浅拷贝存在一个问题,sourceObj的friends属性是一个引用类型的数组对象,浅拷贝只是实现了targetObj和sourceObj指向同一个引用,如果这时候修改targetOb.friends的值,sourceObj.fiends的值也会受影响:

1
2
targetObj.friends.push ('xuxu');
console.log(sourceObj.friends, targetObj.friends) // ['XuXiaojuan', 'GuJuanjuan', 'xuxu']

到现在有些人应该看出来了,要实现targetObj与sourceObj之间没有任何关联,要求如果源对象存在对象属性,那么需要进一步进行一层层递归拷贝,从而保证拷贝的对象与源对象完全隔离。这就是我们所说的深拷贝

深拷贝js对象

JSON对象的parse和stringify

JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深复制。

1
2
3
4
5
6
7
8
9
10
var sourceObj = {
name: 'jessica',
age: 27,
job: 'web',
friends: ['XuXiaojuan', 'GuJuanjuan']
}
var targetObj = JSON.parse(JSON.stringify(sourceObj));
targetObj.friends.push ('xuxu');
console.log(sourceObj) //['XuXiaojuan', 'GuJuanjuan']

从代码的输出可以看出,复制后的targetObj与sourceObj是完全隔离的,二者不会相互影响。

这个方法使用较为简单,可以满足基本的深复制需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深复制(而且会直接丢失相应的值),同时如果对象中存在循环引用的情况也无法正确处理,并且这会抛弃对象的constructor,也就是深复制之后,无论这个对象原本的构造函数是什么,在深复制之后都会变成Object。

首先我们要考虑两个问题,带着这两个问题去创建深度复制函数:

  1. 对于任何对象,它可能的类型有Boolean, Number, Date, String, RegExp, Array 以及 Object(所有自定义的对象全都继承于Object)
  2. 我们必须保留对象的构造函数信息(从而使新对象可以使用定义在prototype上的函数)

下面是根据这两个思路编写的简单的深拷贝代码,当然实现深度复制代码的方法有很多种,这只是其中的一种:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var sourceObj = {
name: 'jessica',
age: 27,
job: 'web',
friends: ['XuXiaojuan', 'GuJuanjuan']
}
//util作为判断变量具体类型的辅助模块
var util = (function(){
var class2type = {};
var objTypes = ["Null","Undefined","Number","Boolean","String","Object","Function","Array","RegExp","Date"];
objTypes.forEach(function(item){
class2type["[object "+ item + "]"] = item.toLowerCase();
})
function isType(obj, type){
return getType(obj) === type;
}
function getType(obj){
return class2type[Object.prototype.toString.call(obj)] || "object";
}
return {
isType:isType,
getType:getType
}
})();
//deep参数用来判断是否是深度复制
function copy(obj,deep){
//如果obj不是对象,那么直接返回值就可以了
if(obj === null || typeof obj !== "object"){
return obj;
}
  //定义需要的局部变量,根据obj的类型来调整target的类型
var i, target = util.isType(obj,"array") ? [] : {},value,valueType;
for(i in obj){
value = obj[i];
valueType = util.getType(value);
     //只有在明确执行深复制,并且当前的value是数组或对象的情况下才执行递归复制
if(deep && (valueType === "array" || valueType === "object")){
target[i] = copy(value);
}else{
target[i] = value;
}
}
return target;
}
// var targetObj = JSON.parse(JSON.stringify(sourceObj));
var targetObj = copy(sourceObj, true);
targetObj.friends.push ('xuxu');
console.log(sourceObj) // ['XuXiaojuan', 'GuJuanjuan']

通过看博客发现还有一个比较优美的方法,这里一并贴出来

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Object.prototype.clone = function () {
var Constructor = this.constructor;
var obj = new Constructor();
for (var attr in this) {
if (this.hasOwnProperty(attr)) {
if (typeof(this[attr]) !== "function") {
if (this[attr] === null) {
obj[attr] = null;
}
else {
obj[attr] = this[attr].clone();
}
}
}
}
return obj;
};
/* Method of Array*/
Array.prototype.clone = function () {
var thisArr = this.valueOf();
var newArr = [];
for (var i=0; i<thisArr.length; i++) {
newArr.push(thisArr[i].clone());
}
return newArr;
};
/* Method of Boolean, Number, String*/
Boolean.prototype.clone = function() { return this.valueOf(); };
Number.prototype.clone = function() { return this.valueOf(); };
String.prototype.clone = function() { return this.valueOf(); };
/* Method of Date*/
Date.prototype.clone = function() { return new Date(this.valueOf()); };
/* Method of RegExp*/
RegExp.prototype.clone = function() {
var pattern = this.valueOf();
var flags = '';
flags += pattern.global ? 'g' : '';
flags += pattern.ignoreCase ? 'i' : '';
flags += pattern.multiline ? 'm' : '';
return new RegExp(pattern.source, flags);
};

定义在Object.prototype上的clone()函数是整个方法的核心,对于任意一个非js预定义的对象,都会调用这个函数。而对于所有js预定义的对象,如Number,Array等,就通过一个辅助clone()函数来实现完整的克隆过程。