目录
一、启言
赋值很简单,我们先了解一下什么是深浅拷贝。而在了解深浅拷贝之前,我们得再了解一下js中的数据类型和他们在内存中的分配形式。
首先,js共有 8 种数据类型,其中大体可分为基本数据类型和引用数据类型。ES6新增的Symbol 和 谷歌67版本中出现的bigInt——是指安全存储、操作大整数。
基本数据类型:String、Number、Boolean、Null、Undefined、Symbol、BigInt
引用数据类型:Object(Object包含function函数、array数组、date日期等)
二、那什么是浅拷贝和深拷贝?
举个栗子🌰 ,当我们想对一个引用数据类型(后面用Object代替)进行拷贝的时候,我们改变新的Object之后,原本的Object内的值也会发生改变,这说明我们拷贝的不够完全、不够深,所以叫浅拷贝。那么当我们完完全全的拷贝了这个Object时,这种方法就叫作深拷贝,
三、数据类型、深浅拷贝的区别?
1、数据类型内存分配形式的不同
基本数据类型是直接存储在栈(stack)中的数据
引用数据类型是在栈中存有指针,实体数据存在堆中,通过栈中的指针找到堆中引用数据类型的实体
2、数据类型复制变量时的结果不同
基本数据类型在重新赋值给一个新的变量以后,2个变量是独立的,因为是把值拷贝了一份
引用数据类型则是复制了一个指针,2个变量指向的值是该指针所指向的内容,一旦一方修改,另一方也会受到影响
3、深浅拷贝的深度不同
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
四、赋值和浅拷贝的区别?
当我们把一个对象赋值给一个新的变量时,赋给这个变量的其实是该对象的在栈中的地址,而不是对象在堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
而浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值; 如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
举一个数组(引用数据类型)简单的赋值例子:
const arr_1 = [1]
const arr_2 = arr_1
arr_1[0] = 2
console.log(arr_1, arr_2)
// [2] [2]
// arr_1内的值发生改变,arr_2也受到了影响
五、浅拷贝的实现
拿数组举例,前提:创建一个新的数组,而不是赋值
// 第 1 种方法,Array.from
const arr = [{ a: 1 }, { b: 2 }, { c: 3 }]
const newArr = Array.from(arr)
console.log(newArr) // [ { a: 1 }, { b: 2 }, { c: 3 } ]
arr[0].a = 0
console.log(newArr) // [ { a: 0 }, { b: 2 }, { c: 3 } ]
举一反三,其他会返回一个 相同的 新数组 的方法也可以实现浅拷贝
// 第 2 种方法,比如slice
const arr_1 = [{ a: 1 }, { b: 2 }, { c: 3 }]
const newArr_1 = arr_1.slice()
console.log(newArr_1) // [ { a: 1 }, { b: 2 }, { c: 3 } ]
arr_1[0].a = 0
console.log(newArr_1) // [ { a: 0 }, { b: 2 }, { c: 3 } ]
// 第 3 种方法,比如concat
const arr_2 = [{ a: 1 }, { b: 2 }, { c: 3 }]
const newArr_2 = arr_2.concat()
console.log(newArr_2) // [ { a: 1 }, { b: 2 }, { c: 3 } ]
arr_2[0].a = 0
console.log(newArr_2) // [ { a: 0 }, { b: 2 }, { c: 3 } ]
浅拷贝对象
// 用Object.assign浅拷贝一个对象
const obj = {
a: {
b: 1
} };
const copy = Object.assign({}, obj);
console.log(copy) // { a: { b: 1 } }
obj.a.b = 2
console.log(copy) // { a: { b: 2 } }
// 用 ... 浅拷贝一个对象
const newObj = { ...obj }
六、深拷贝的实现
// 第 1 种方法,依赖函数库
import _ from 'lodash'
const obj_1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3],
}
const new_obj_1 = _.cloneDeep(obj_1)
console.log(obj_1.b.f === new_obj_1.b.f)
// false
// 第 2 种方法,JSON.stringify() + JSON.parse()
const obj_2 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3],
}
const new_obj_2 = JSON.parse(JSON.stringify(obj_2))
console.log(obj_2.b.f === new_obj_2.b.f)
// false
// 第 3 种方法,手写递归
function deepClone(args) {
const newArgs = args instanceof Array ? [] : {}
// 对象object就深入,不是对象object就返回
if (typeof args !== 'object') {
return args
} else {
for (const i in args) {
newArgs[i] = typeof args[i] == 'object' ? deepClone(args[i]) : args[i]
}
return newArgs
}
}
const obj_3 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3],
}
const new_obj_3 = deepClone(obj_3)
console.log(obj_3.b.f === new_obj_3.b.f)
// false
⚠️ 缺点:递归容易出现爆栈问题
⭐️ 优化方案:
1、将递归改为循环,就不会出现爆栈问题了
2、使用hash递归
// 有可能碰到循环引用的问题 const a = {}; a.a = a; deepCopy(a) //会造成一个死循环
function isObject(obj) {
return (
Object.prototype.toString.call(obj) === '[object Object]' ||
Object.prototype.toString.call(obj) === '[object Array]'
)
}
function deepCopy_4(obj, hash = new Map()) {
if (!isObject(obj)) return obj // 检测是否为对象或者数组
let newObj = Array.isArray(obj) ? [] : {}
if (hash.has(obj)) return hash.get(obj) // 解决循环引用问题
hash.set(obj, newObj)
console.log(obj) // <ref *1> { a: [Circular *1] }
for (const i in obj) {
console.log(i) // a
console.log(obj[i]) // <ref *1> { a: [Circular *1] }
console.log(isObject(obj[i])) // true
if (isObject(obj[i])) {
newObj[i] = deepCopy_4(obj[i], hash)
} else {
newObj[i] = obj[i]
}
}
return newObj
}
3、干净版
function isObject(obj) {
return (
Object.prototype.toString.call(obj) === '[object Object]' ||
Object.prototype.toString.call(obj) === '[object Array]'
)
}
function deepClone(obj, hash = new Map()) {
// 判断类型
if (!isObject(obj)) return obj
let newObj = obj instanceof Array ? [] : {}
// 解决循环引用问题
if (hash.has(obj)) return hash.get(obj)
hash.set(obj, newObj)
// 进行拷贝
if (obj instanceof Array) {
newObj = obj
} else {
for (const key in obj) {
newObj[key] = deepClone(obj[key], hash)
}
}
return newObj
}