网站改版重新收录住房与城乡建设部网站EPC
目录
- 浅拷贝
- 实现
- 深拷贝
- 实现
- 自己手写
浅拷贝
浅拷贝是指创建一个新对象,这个对象具有原对象属性的精确副本
-
基本数据类型(如字符串、数字等),在浅拷贝过程中它们是通过值传递的,而不是引用传递,修改值并不会影响原对象
-
如果这些属性是引用类型(如对象、数组等),浅拷贝只会复制它们的引用,而不会复制它们的内容
-
浅拷贝后的新对象和原对象中的引用类型属性仍然指向相同的内存地址,修改其中一个的引用类型数据,会影响另一个
实现
-
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)// 遍历普通的keyfor (const key in originValue) {newObj[key] = deepCopy(originValue[key], map);}// 单独遍历symbolconst symbolKeys = Object.getOwnPropertySymbols(originValue)for (const symbolKey of symbolKeys) {newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map)}return newObj
}
