在讨论深拷贝和浅拷贝的之前,我们先来了解一下简单简单类型和复杂类型
1、简单类型和复杂类型
简单类型又叫基本数据类型或值类型,复杂类型又叫做引用类型
-
简单数据类型:在存储变量中存储的是值的本身,因此叫做值类型
-
引用类型:在存储变量中存储的仅仅是地址(引用),因此叫做引用类型
我们前面说过,js包括了6种基本类型(Number
、String
、Boolean
、Undefined
、Null
、Symbol
)和1种引用类型(Object
)
- 基本类型没有属性
- 基本类型的初始化只能使用字面量形式
// 虽然基本类型没有属性,但是为它添加属性并不会报错
let namer = '王五'
namer.age = 2
console.log(namer.age); // undefined
// 使用new就会创建一个对象
let namer1 = '张三'
let namer2 = new String('李四')
console.log(typeof namer1); // string
console.log(typeof namer2); // object
复制代码
堆和栈
堆栈空间分配的区别:
- 栈:基本数据类型就是存放在栈里面,由系统空间自动分配释放函数的参数值、局部变量的值等;
- 堆:引用类型就是存储在堆里面,一般由程序员分配释放或由垃圾收机制回收(js就是由垃圾会收器回收的)
引用类型变量存储的是一个地址,真正的对象实例存储在堆空间中
2、深拷贝 和 浅拷贝
我们都知道了,对象就是存储现在堆中,以变量-值这样的形式来存储,内容存储在堆中,通过引用进行访问,其中引用表示存储在栈中;而基本数据类型就存储在栈中
深拷贝:复制对象的实例,相当于在内存开辟一个新的地址来存储这个变量,所以,修改某个对象,跟另一个变量半毛钱关系都没有,你走你的阳关道,我过我的独木桥
浅拷贝:直接复制对象的地址,复制之后两个对象指向同一个地址。所以修改任何一个对象,由于他们指向同一个对象,所以另一个变量也会跟着改变
我们可以使用 ===
来判断深浅拷贝
- 浅拷贝:由于来自同一个地址,返回
true
- 深拷贝:来自不同的地址,返回
false
let obj1 = { namer: '张三' }
let obj2 = obj1
obj2.namer = '李四'
console.log(obj1); // { namer: '张三' }
console.log(obj2); // { namer: '张三' }
console.log(obj1 === obj2); // true
let arr1 = [1, 2, 3]
let arr2 = arr1
arr1[0] = '张三'
console.log(arr1); // [ '张三', 2, 3 ]
console.log(arr2); // [ '张三', 2, 3 ]
console.log(arr1 === arr2); // true
复制代码
可见对象之间直接赋值的方式就是浅拷贝
我们一般复制的语法有这几种:...
、slice
、concat
、Object.assign
、JSON.stringify
3、数组拷贝 slice
、concat
数组的两个方法:slice
(截取)、concat
(拼接),本质上就是一种复制
slice
let arr1 = [1, 2, '张三']
let arr2 = arr1.slice()
arr2[2] = '李四'
console.log(arr1); // [1, 2, '张三']
console.log(arr2); // [1, 2, '李四']
console.log(arr1 === arr2); // false
复制代码
concat()
let arr1 = [1, 2, '张三']
// 拼接,不传入参数的时候,返回原数组
let arr2 = arr1.concat();
arr2[2] = '李四'
console.log(arr1); // [1, 2, '张三']
console.log(arr2); // [1, 2, '李四']
console.log(arr1 === arr2); // false
复制代码
很明显这两种复制方式方式都是深复制
但是将它们还有另外一个特点,请看下面:
slice
let arr1 = [1, 2, '张三', ['1', '2']]
let arr2 = arr1.slice()
arr2[3][1] = '李四'
console.log(arr1); // [ 1, 2, '张三', [ '1', '李四' ] ]
console.log(arr2); // [ 1, 2, '张三', [ '1', '李四' ] ]
console.log(arr1 === arr2); // false
复制代码
concat
let arr1 = [1, 2, '张三', ['1', '2']]
let arr2 = arr1.concat()
arr2[3][1] = '李四'
console.log(arr1); // [ 1, 2, '张三', [ '1', '李四' ] ]
console.log(arr2); // [ 1, 2, '张三', [ '1', '李四' ] ]
console.log(arr1 === arr2); // false
复制代码
我们可以看出,当数组的属性为应用类型的时候,这两种复制方式对于引用类型其实是浅拷贝,拷贝之后,应用类型的指针还是指向同一个存储地址
总结:
slice
、concat
可用于数组的拷贝- 当拷贝的数组元素都是基本类型的时候,
slice
、concat
进行的是深拷贝 - 当拷贝的数组元素有引用类型的时候,对引用类型的拷贝是浅拷贝,基本类型仍然是深拷贝
- 所以,
slice
、concat
仅适用于拷贝不包含引用类型的数组
4、对象拷贝...
、Object.assign
...
// 复制数组
let arr1 = [1, 2, '张三', ['1']]
let arr2 = [...arr1]
arr1[2] = '李四'
arr1[3][0] = '张三不是李四'
console.log(arr1); // [ 1, 2, '李四', [ '张三不是李四' ] ]
console.log(arr2); // [ 1, 2, '张三', [ '张三不是李四' ] ]
console.log(arr1 === arr2); // false
// 复制对象
let obj1 = { namer: '张三', hobby: ['螺蛳粉'] }
let obj2 = {...obj1 }
obj2.namer = '李四'
obj2.hobby[0] = '牛肉果条'
console.log(obj1); // { namer: '张三', hobby: [ '牛肉果条' ] }
console.log(obj2); // { namer: '李四', hobby: [ '牛肉果条' ] }
console.log(obj1 === obj2); // false
复制代码
ES6新增的拷贝语法:Object.assign
// 拷贝数组
let arr1 = [1, 2, '张三', ['1']]
let arr2 = []
Object.assign(arr2, arr1)
arr1[2] = '李四'
arr1[3][0] = '张三不是李四'
console.log(arr1); // [ 1, 2, '李四', [ '张三不是李四' ] ]
console.log(arr2); // [ 1, 2, '张三', [ '张三不是李四' ] ]
console.log(arr1 === arr2); // false
// 拷贝对象
let obj = { namer: '张三', hobby: ['螺蛳粉'] }
let copyObj = {}
// 拷贝
Object.assign(copyObj, obj)
console.log(copyObj); // { namer: '张三', hobby: ['螺蛳粉'] }
copyObj.namer = '李四'
obj.hobby[0] = '牛肉果条'
console.log(obj); // { namer: '张三', hobby: [ '牛肉果条' ] }
console.log(copyObj); // { namer: '李四', hobby: [ '牛肉果条' ] }
复制代码
总结:
...
和Object.assign
可用于拷贝对象,,它们的拷贝特性还是跟slice
和concat
一样- 对象的值是基本类型,进行深拷贝;值是引用类型进行浅拷贝
5、JSON.stringify
JSON.stringify()
就是目前开发中最常用的深拷贝方式,它的原理就是把对象转化成字符串保存在内存中,然后再利用 JSON.parse()
将字符串转化成一个新的对象
let obj = { namer: '张三', hobby: ['螺蛳粉'] }
let obj1 = JSON.parse(JSON.stringify(obj))
console.log(obj1); // { namer: '张三', hobby: [ '螺蛳粉' ] }
console.log(typeof obj1); // object
obj1.hobby[0] = '牛肉果条'
console.log(obj); // { namer: '张三', hobby: [ '螺蛳粉' ] }
console.log(obj1); // { namer: '张三', hobby: [ '牛肉果条' ] }
复制代码