JS的Ctrl+C形式 — 深拷贝、浅拷贝

在讨论深拷贝和浅拷贝的之前,我们先来了解一下简单简单类型和复杂类型

1、简单类型和复杂类型

简单类型又叫基本数据类型或值类型,复杂类型又叫做引用类型

  1. 简单数据类型:在存储变量中存储的是值的本身,因此叫做值类型

  2. 引用类型:在存储变量中存储的仅仅是地址(引用),因此叫做引用类型

我们前面说过,js包括了6种基本类型(NumberStringBooleanUndefinedNullSymbol)和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
复制代码

堆和栈

堆栈空间分配的区别:

  1. 栈:基本数据类型就是存放在栈里面,由系统空间自动分配释放函数的参数值、局部变量的值等;
  2. 堆:引用类型就是存储在堆里面,一般由程序员分配释放或由垃圾收机制回收(js就是由垃圾会收器回收的)

引用类型变量存储的是一个地址,真正的对象实例存储在堆空间中

img

2、深拷贝 和 浅拷贝

我们都知道了,对象就是存储现在堆中,以变量-值这样的形式来存储,内容存储在堆中,通过引用进行访问,其中引用表示存储在栈中;而基本数据类型就存储在栈中

深拷贝:复制对象的实例,相当于在内存开辟一个新的地址来存储这个变量,所以,修改某个对象,跟另一个变量半毛钱关系都没有,你走你的阳关道,我过我的独木桥

img

浅拷贝:直接复制对象的地址,复制之后两个对象指向同一个地址。所以修改任何一个对象,由于他们指向同一个对象,所以另一个变量也会跟着改变

img

我们可以使用 === 来判断深浅拷贝

  1. 浅拷贝:由于来自同一个地址,返回 true
  2. 深拷贝:来自不同的地址,返回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
复制代码

可见对象之间直接赋值的方式就是浅拷贝

我们一般复制的语法有这几种:...sliceconcatObject.assignJSON.stringify

3、数组拷贝 sliceconcat

数组的两个方法: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
复制代码

我们可以看出,当数组的属性为应用类型的时候,这两种复制方式对于引用类型其实是浅拷贝,拷贝之后,应用类型的指针还是指向同一个存储地址

总结:

  1. sliceconcat 可用于数组的拷贝
  2. 当拷贝的数组元素都是基本类型的时候,sliceconcat进行的是深拷贝
  3. 当拷贝的数组元素有引用类型的时候,对引用类型的拷贝是浅拷贝,基本类型仍然是深拷贝
  4. 所以,sliceconcat仅适用于拷贝不包含引用类型的数组

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: [ '牛肉果条' ] }
复制代码

总结:

  1. ...Object.assign 可用于拷贝对象,,它们的拷贝特性还是跟sliceconcat 一样
  2. 对象的值是基本类型,进行深拷贝;值是引用类型进行浅拷贝

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: [ '牛肉果条' ] }
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享