目录
- 浅拷贝
- 实现
- 深拷贝
- 实现
- 自己手写
浅拷贝
浅拷贝是指创建一个新对象,这个对象具有原对象属性的精确副本
-
基本数据类型(如字符串、数字等),在浅拷贝过程中它们是通过值传递的,而不是引用传递,修改值并不会影响原对象
-
如果这些属性是引用类型(如对象、数组等),浅拷贝只会复制它们的引用,而不会复制它们的内容
-
浅拷贝后的新对象和原对象中的引用类型属性仍然指向相同的内存地址,修改其中一个的引用类型数据,会影响另一个
实现
-
Object.assign(target, source)
:用于将所有可枚举的属性从一个或多个源对象复制到目标对象,返回目标对象const obj = { name: "obj", age: 18, height: 180, o: { a: 1, b: 2, }, }; const copy = Object.assign({ height: 188 }, obj); console.log(copy); // {height: 180, name: 'obj', age: 18, o:{...}} copy.name = "copy"; console.log(obj.name); // obj console.log(copy.name); // copy copy.o.a = 11; console.log(copy.o); // {a: 11, b: 2} console.log(obj.o); // {a: 11, b: 2}
-
数组的
slice()
方法:对于数组,slice()
方法可以用来进行浅拷贝。它返回一个新数组,并将原数组中的元素逐个复制到新数组中,但如果数组的元素是对象,它们仍然共享相同的引用const names = ["abc", "def", { name1: "ghi", name2: "cba" }]; const copy = names.slice(1); console.log(copy); // ['def', {name1: 'ghi', name2: 'cba'}] copy[1].name1 = "abc"; console.log(copy[1]); // {name1: 'abc', name2: 'cba'} console.log(names[2]); // {name1: 'abc', name2: 'cba'}
-
扩展运算符(spread operator
...
):适用于数组和对象const obj = { name: "obj", age: 18, height: 180, o: { a: 1, b: 2, }, }; const names = ["abc", "def", { name1: "ghi", name2: "cba" }]; const copy1 = { ...obj }; const copy2 = [ ...names ]; copy1.o.a = 11; console.log(copy1.o); // {a: 11, b: 2} console.log(obj.o); // {a: 11, b: 2} copy2[1].name1 = "abc"; console.log(copy2[2]); // {name1: 'abc', name2: 'cba'} console.log(names[2]); // {name1: 'abc', name2: 'cba'}
实现的图解:
浅拷贝对基本数据类型有效,但对于对象、数组等引用类型,只是复制了它们的引用,这会导致在修改拷贝时,原对象也被修改。如果需要对嵌套对象和数组进行完全独立的拷贝,则需要使用深拷贝
深拷贝
深拷贝是指将一个对象的所有属性都完整地复制到另一个对象中,包括嵌套的对象或数组。深拷贝与浅拷贝不同,浅拷贝只复制对象的引用,而深拷贝会递归地复制对象的所有层次,确保原始对象和新对象完全独立,任何一方的修改不会影响另一方
实现
-
JSON
实现:这是最简单的一种方式,适合处理不包含函数、undefined
、Symbol
、循环引用等特殊类型的对象,序列化有问题的情况如下:-
undefined
不会被序列化,且在对象属性值中会被删除,在数组中则会被转化为null
-
Symbol
是唯一的标识符,无法被序列化,且会被丢弃 -
Date
对象会被序列化为字符串,但当反序列化时,它不再是Date
对象,而是一个普通字符串 -
如果对象有循环引用,
JSON.stringify
会抛出错误,因为它无法处理递归结构 -
Map
和Set
结构会被序列化为空对象,并且在反序列化时,无法恢复为原始结构 -
不会序列化对象的原型链属性,因此对象的继承关系会丢失
const set = new Set(); const obj = { name: "obj", age: 18, height: undefined, o: { a: 1, b: 2, }, [Symbol()]: "symbol", [set]: set, date: new Date() }; console.log(JSON.parse(JSON.stringify(obj))); // {name: 'obj', age: 18, o: {…}, [object Set]: {}, date: "2024-09-14T06:48:44.497Z"}
-
-
使用
structuredClone()
:在一些现代浏览器中,可以使用内置的structuredClone()
来实现深拷贝。它可以处理大多数情况下的深拷贝需求,包括循环引用、Date
、Map
和Set
等-
不能拷贝
Symbol
属性,Symbol
类型属性会被忽略,因为Symbol
是唯一的标识符,具有不可枚举性和唯一性 -
拷贝
Symbol
值会报错,Failed to execute 'structuredClone' on 'Window': Symbol() could not be cloned
-
不会拷贝对象的原型链属性
const obj = { name: "Alice", age: undefined, [Symbol()]: "symbol", // 会忽略 }; const clone = structuredClone(obj); console.log(clone); // { name: 'Alice', age: undefined }
-
-
使用
Lodash
库中的_.cloneDeep()
:Lodash 是一个非常流行的 JavaScript 工具库,其中提供了_.cloneDeep()
方法,可以轻松实现深拷贝,对于大部分普通对象、数组、Set
和Map
能够正确处理并进行深拷贝- 无法深拷贝
Symbol
属性,但可以克隆对象中以Symbol
作为值的属性
const sym = Symbol('id'); const obj = { [sym]: 'value', id: Symbol('id'), name: 'Alice' }; const clone = _.cloneDeep(obj); console.log(clone); // { id: Symbol(id), name: 'Alice' } -- Symbol 属性被忽略,Symbol 值被正确克隆
- 无法深拷贝
自己手写
- 实现对对象和基本数据类型的拷贝
- 对
Symbol
的key
进行处理 - 其他数据类型的值进程处理:数组、函数、
Symbol
、Set
、Map
- 对循环引用的处理
function deepCopy(originValue, map = new WeakMap()) {
// 0.如果值是Symbol的类型
if (typeof originValue === "symbol") {
return Symbol(originValue.description)
}
// 1.如果是原始类型, 直接返回
if (!isObject(originValue)) {
return originValue
}
// 2.如果是set类型
if (originValue instanceof Set) {
const newSet = new Set()
for (const setItem of originValue) {
newSet.add(deepCopy(setItem))
}
return newSet
}
// 3.如果是函数function类型, 不需要进行深拷贝
if (typeof originValue === "function") {
return originValue
}
// 4.如果是对象类型, 才需要创建对象
if (map.get(originValue)) {
return map.get(originValue)
}
const newObj = Array.isArray(originValue) ? []: {}
map.set(originValue, newObj)
// 遍历普通的key
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key], map);
}
// 单独遍历symbol
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const symbolKey of symbolKeys) {
newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map)
}
return newObj
}