JS中的深浅拷贝

为什么要进行拷贝

首先我们知道JS中的数据类型分为基本类型和引用类型:

  • 基本类型:Number, Stirng, Boolean, Null, Undefined :(赋值)
  • 引用类型:Object :(赋

其中基本类型存放在内存栈中,而引用类型存放在内存堆中。因此在对基本类型进行复制时,(var i = 0; var j = i),将会重新开辟一个新的内存存放该基本类型,新旧数据互不干扰。而引用类型的变量其实更像一个指针(个人理解),因此在对新的变量赋值时,其实只是将地址赋值给这个变量,所以新旧变量其实指向的是同一数据,当对其中一个对象进行操作时,会影响到另一个变量。

1
2
3
4
5
6
7
8
9
var obj1 = {
value: 'a'
}

var obj2 = obj1

obj2.value = 'b'

console.log(obj1.value) // a

浅拷贝

拷贝的是地址,最终两个变量指向同一份数据,修改其中一个变量会改变另一个。

使用原生的 Object.assgin() 就可以进行第一级属性的深拷贝(但并不是深拷贝)

1
2
3
4
5
6
7
var obj1 = {
value: 'a'
}
var obj2 = Object.assign({}, obj1)
obj2.value = 'b'

console.log(obj1.value) // b

而如果拷贝的对象当中包含对象(即拷贝对象为多级属性),那么 Object.assign() 方法只会拷贝对象的引用地址。

1
2
3
4
5
6
7
8
9
var obj1={
value: 'a',
obj3:{
value2: 'c'
}
}
var obj2 = Object.assign({},obj1);
obj2.obj3.value2='b';
console.log(obj1);//{ value: 'a', obj3:{ value2: 'b' } }

深拷贝

两个变量是独立的,互不影响。

JSON.parse(JSON.stringify(object))

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = {
student: {
name: 'xx'
},
say: function() {
return '你好'
},
un: undefined
}

var b = JSON.parse(JSON.stringify(a)) // {student: {name: "xx"}}
b.student.name = 'kk'
a.student.name // "xx"

缺点:

  1. 会忽略 undefined
  2. 会忽略 symbol
  3. 不能序列化函数
  4. 不能解决循环引用的对象
  5. 不能正确处理 new Date()
  6. 不能处理正则

递归实现

  1. 参数类型做校验,如果不是对象则直接返回
  2. 初始化新属性 对象|数组
  3. 对值为对象的属性继续深入拷贝/递归
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function deepCopy(source){
// 如果参数不是对象直接返回
if(typeof source !== 'object') return source
// 初始化新属性 对象或者是数组
var newObj = Object.prototype.toString.call(source) === '[Object Array]' ? [] : {}

for(var key in source){
// 判断该属性是否在该对象实例上
if(source.hasOwnProperty(key)){
// 判断该属性的值是否是对象,如果是则对该对象进行拷贝
newObj[key] = typeof source[key] === 'object' ? deepCopy(source(key)) : source[key]
}
}

return newObj
}