直接赋值

基本数据类型保存在栈里面,可以直接访问它的值,赋值时,会完整复制变量值。

let a = 10
let b = a
a = 20
console.log(b) //10

引用数据类型保存在里面,栈里面保存的是地址,通过栈里面的地址去访问堆里面的值。

let obj1 = {
a : 1
}
let obj2 = obj1
obj1.a = 2
console.log(obj2.a) //2

img

这是直接赋值的情况,要想两个值互不影响,就需要进行拷贝,js中的拷贝分为浅拷贝和深拷贝

浅拷贝

我的理解就是,浅拷贝实际上只拷贝了一层

具体实现方法有这几种:

  1. 拓展运算符
let a = [1,2,3]
let b = [...a] //把数组a的内容挨个取出放到b数组中
a[0] = 0
console.log(b) //1,2,3
  1. slice方法
let a = [1,2,3]
let b = a.slice()
a[0] = 4;
console.log(b) //1 2 3
  1. assign方法
let obj1 = { a: 1, b: 2 }
let obj2 = Object.assign({}, obj1)
obj1.a = 5
console.log(obj2); // { a: 1, b: 2 }
  1. 手搓
function shallowCopy (obj){
// 只拷贝复杂类型,基本类型或null直接返回
if(typeof obj !== 'object' || obj === null) {
return obj;
}
// 判断是新建一个数组还是对象
let newObj = Array.isArray(obj) ? []: {};
//for…in会遍历对象的整个原型链,如果只考虑对象本身的属性,需要搭配hasOwnProperty
for(let key in obj ){
//hasOwnProperty判断是否是对象自身属性,会忽略从原型链上继承的属性
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key];//只拷贝对象本身的属性
}
}
return newObj;
}

但是如果有嵌套的话,浅拷贝就会出问题

let obj1 = { a: 1, b: { c: 2 } }
let obj2 = Object.assign({}, obj1)
obj1.b.c = 5
console.log(obj2); // { a: 1, b: { c: 5 } }

因为这里复制的b的属性是引用类型,所以复制的还是它的内存地址

深拷贝

复制对象及其所有嵌套对象和数组,确保新对象与原对象完全独立

下面是具体实现方法:

1.使用JSON

let a = [ [1],[2], [3]]
let b = JSON.parse(JSON.stringfy(a));
a[0][0] = 4;
console.log(a) // [ [4],[2], [3]]
console.log(b) // [ [1],[2], [3]]

利用JSON.stringify将对象序列化成为JSON字符串,并将对象里面的内容转换成字符串,再使用JSON.parse来反序列化,将字符串生成一个新的对象

这个方法虽然简单,但也存在一些问题:

  • ⽆法解决循环引用问题
  • 无法拷贝一些特殊的对象,如 RegExp (会变成空对象)、 Date (被转成字符串)
  • 无法拷贝函数
  • 无法拷贝undefined

2.使用第三方库 lodash

const _ = require('lodash');  //导入lodash库
let obj = {
a: 1,
b: {
c: 2
}
};
let deepCopy = _.cloneDeep(obj);

3.structuredClone

let a = [ [1],[2], [3]]
let b = structuredClone(a);
a[0][0] = 4;
console.log(a) // [ [4],[2], [3]]
console.log(b) // [ [1],[2], [3]]

这个是一个web api,只适用于在浏览器环境下使用

4.手搓(递归)

let newObj = {}
function deepClone(newO,old){
for(let key in old) {
let value = old[key]
if(value instanceof Array) {
newO[key] = [] //被拷贝的是数组,创建一个新数组
deepClone(newO[key],value)
}else if(value instanceof Object) {
newO[key] = {} //创建新对象
deepClone(newO[key],value)
}
}else {
newO[key] = value //基础类型,单纯值复制
}
}
deepClone(newObj,oldObj)

循环引用问题

let oneObj = {
name: 'oneObj'
}
let twoObj = {
name: 'twoObj',
age: 15,
family: oneObj //two引用one
}
oneObj.a = twoObj //one引用two造成循环引用

如果直接按照上面的递归来深拷贝,就会陷入无线循环

解决方法:ES6 的Map对象,它的key可以是任意类型(对象类型)

let newObj = {}
function deepClone(newO,twoObj,map) {
if(!map){
map = new Map()
}
for(let key in twoObj) {
let value = twoObj[key]
if(value instanceof Array) {
newO[key] = []
deepClone(newO[key],value)
}else if(value instanceof Object) {
newO[key] = {}
if(!map.has(value)) { //如果这个对象在map里出现过,就不进入递归拷贝
map.set(value,1)
deepClone(newO[key],value,map)
}

} else {
newO[key] = value
}
}
}
deepClone(newObj,twoObj)