前言
此文部分来自于公众号作者 大迁世界
本站只做记录使用,如有侵权,请联系站长删除!
所谓深浅拷贝,其实都是进行复制,主要区别在于复制出来的新对象和原来的对象时候会相互影响。
深浅拷贝的区分:B 复制 A,如何A 发生变化 B跟着变化 浅拷贝
反之, 如果 B 不发生变化 则为深拷贝。
浅拷贝 A B 指向同一个地址 所以 A受影响 B也会受影响
1. 深浅拷贝实例
浅拷贝
1 2 3 4 5 6
| var a =[1,2,3,4] var a_clone = a; console.log(a === a_clone); a_clone[0] = 5; console.log(a_clone); console.log(a);
|

深拷贝
1 2 3 4 5
| var obj=[1,2,3,[4,5]]; var obj_extend =$.extend(true,{},obj); console.log(obj_extend === obj); console.log(obj_extend); console.log(obj);
|

要深入了解深浅拷贝必选先要了解其原理,这就不得不说到ECMAScript中的数据类型。
2. 基本类型和引用类型
2.1 分类
基本类型:undefined,null,布尔值(Boolean),字符串(String),数值(Number)
引用类型:统称为Object类型,细分为:Function、Date、Array、Object类型等。
2.2 不同的数据存储形式
简单来说, 基本数据类型
保存在栈内存
引用类型
保存在堆内存
2.2.1 栈内存
栈内存中分别存储着变量的标识符以及变量的值

即:var a = "A"
在栈内存中是这样的

2.1.2 堆内存
栈内存存储的是变量的标识符以及对象在堆内存中的存储地址,但需要访问引用类型(对象、数组等)的值时,首先需要从栈中获得该对象的地址指针,然后再从对应的堆内存中取得所需的数据。

即:var a = {name:“jack”};
在内存中是这样的:

3. 不同类型的复制形式
3.1 基本类型的复制
当你在复制时,相当于把值也一并复制给了新的变量
1 2 3 4 5 6
| var a = 1; var b = a; console.log(a === b); var a = 2; console.log(a); console.log(b);
|

改变a的值,并不会影响到b。



3.2 引用类型的复制
当复制引用类型时,实际上只是赋值起指向的内存地址,即原来的变量与复制的新变量指向了同一个东西
1 2 3 4 5 6
| var a ={name:"jack",age:20}; var b = a; console.log(a === b); a.age =30; console.log(a); console.log(b)
|

可以看得出来:改变a的值,也会影响b的值
内存中是这样的
1
| var a = {name:"jack",age:20}
|



看完上面之后,是不是就明白了。所谓深浅拷贝:
对于仅仅是复制了引用(地址),换言之,原来的变量和新的变量指向了同一个东西,彼此之间的操作会互相影响,为浅拷贝
。
反言之。如果是在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响的,为 深拷贝
深浅拷贝的主要区别:复制的是引用(地址)还是复制的是实例。
接下来就对上述实例进行深拷贝的实现
4. 实现深拷贝方式
4.1 利用递归方式 对属性中所有的引用类型的值,遍历到是基本类型的值为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function deepCLone(obj){ var targetObj = Array.isArray(obj)?[]:{}; for(var keys in obj){ if(obj.hasOwnProperty(keys)){ if(obj[keys] && typeof obj[keys] === 'object'){ targetObj[keys] = deepCLone(obj[keys]) }else{ targetObj[keys] = obj[keys] } } } return targetObj }
var a ={name:"jack",age:20}; var b = deepCLone(a); console.log(b ===a); a.age = 30; console.log(a); console.log(b);
|

4.2 jQuery中的 extend复制方法
可以用来扩展对象,这个方法可以传入一个参数:deep(true or
false),表示是否执行深复制(如果执行深复制则会执行递归复制)
-
深拷贝
1 2 3 4 5 6 7
| var obj = {name:"xixi",age:20,company:{name:"腾讯",address:"深圳"}}; var obj_extend =$.extend(true,{},obj); console.log(obj === obj_extend); obj.company.name="阿里"; obj.name="albabba"; console.log(obj); console.log(obj_extend);
|

-
浅拷贝
1 2 3 4 5 6 7
| var obj = {name:"xixi",age:20,company:{name:"腾讯",address:"深圳"}}; var obj_extend =$.extend(false,{},obj); console.log(obj === obj_extend); obj.company.name="阿里"; obj.name="albabba"; console.log(obj); console.log(obj_extend);
|

从company的变化可以看出,貌似是浅拷贝,但是name又貌似是深拷贝,这是何解。
其实,Array的slice 和concat方法和jQuery中的extend复制方法,都是会复制第一层的值,对于第一层的值都是深拷贝,而到第二层的时候 Array的slice和concat方法就是复制引用,jQuery中的extend复制方法则取决于你的第一个参数,也就是时候进行递归复制。
所谓第一层 就是 key所对应的value值是基本数据类型,也就是像实例中的name,age,而对于value值是引用类型 则为第二层,也就是像上面的company。
slice
1 2 3 4 5 6
| var a =[1,2,3]; var b = a.slice(); console.log(b === a); a[0] = 4; console.log(a); console.log(b);
|

concat
1 2 3 4 5 6
| var a =[1,2,3]; var b = a.concat(); console.log(b === a); a[0] = 4; console.log(a); console.log(b);
|

看似是深拷贝,其实不然,下面的例子就说明了一切
1 2 3 4 5
| var a =[[1,2,3],4,5]; var b= a.slice(); a[0][0] = 6; console.log(a); console.log(b);
|

这就很明显的看出来了,上面所述,第一层是深拷贝 第二层 是引用。
4.3 JSON对象的parse和stringify
- JSON对象中的stringify可以把一个js对象序列化为一个JSON字符串
- parse可以把JSON字符串反序化为一个js对象,这两个方法实现的是深拷贝
1 2 3 4 5 6 7
| var obj ={name:'xixi',age:20,company:{name:'TX',address:'深圳'}}; var obj_json = JSON.parse(JSON.stringify(obj)); console.log(obj === obj_json); obj.name="ali"; obj.company.name="AL"; console.log(obj); console.log(obj_json);
|

5. 如何实现对循环引用的深拷贝
1 2 3
| let obj = { name: '1', address: {x: 100, y: 100}} obj.o = obj
|
obj中的属性o指向obj本身,在深拷贝过程中就会出现循环引用问题
如果还使用上述的算法,就会出现调用栈溢出问题
因此,要解决循环引用问题的话,需要使用一个Map
/WeakMap
来存放已经拷贝过的对象
key为旧对象,value为新对象
在先拷贝属性之前,先去map中查找是否已经被处理过,如果被处理过,直接返回value值
在拷贝属性之前,把新、旧对象保存到map中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function deepClone(obj = {}, hash = new WeakMap()) { if (typeof obj != 'object' || obj == null) { return obj } if (hash.get(obj)) { return hash.get(obj) }
let result if (result instanceof Array) { result = [] } else { result = {} }
hash.set(obj, result)
for (const key in obj) { if (Object.hasOwnProperty.call(obj, key)) { result[key] = deepClone(obj[key], hash) } }
return result }
|
1 2 3 4 5 6
| let obj = { name: '1', address: {x: 100, y: 100}} obj.o = obj obj.address.z = obj console.log(deepClone(obj)) obj.address.x = 140 console.log(obj)
|

6. structuredClone()
JavaScript中深拷贝对象的最简单方法,可以深度复制任何对象。
1 2 3 4 5 6
| const obj = { name: 'Tari', friends: [{ name: 'Messi' }] };
const clonedObj = structuredClone(obj);
console.log(obj.name === clonedObj); console.log(obj.friends === clonedObj.friends);
|
6.1 轻松克隆循环引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const car = { make: 'Toyota', };
car.basedOn = car;
const cloned = structuredClone(car);
console.log(car.basedOn === cloned.basedOn);
console.log(car === car.basedOn);
|
这是永远无法用JSON
stringify
/parse
技巧实现的:
1 2 3 4 5 6 7 8
| const car = { make: 'Toyota', };
car.basedOn = car; const cloned = JSON.parse(JSON.stringify(car)); console.log(car.basedOn === cloned.basedOn); console.log(car === car.basedOn);
|

6.2 可对不限层次的对象进行拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const obj = { a: { b: { c: { d: { e: 'Coding Beauty', }, }, }, }, };
const clone = structuredClone(obj);
console.log(clone.a.b.c.d === obj.a.b.c.d); console.log(clone.a.b.c.d.e);
|
6.3 structuredClone()
的局限性
6.3.1 无法克隆函数或方法
1 2 3 4 5 6 7 8
| const car = { make: 'Toyota', move(){ console.log('vroom vroom...'); } }; car.basedOn = car; const cloned = structuredClone(car);
|

报错信息:"Failed to execute ‘structuredClone’ on ‘Window’: move()’ 表示在尝试使用 structuredClone
方法复制或序列化一个对象时遇到了问题。structuredClone
是一个用于深度复制对象的方法,它能够正确复制包括函数在内的所有值。
问题可能出现在 move()
函数上,因为在尝试序列化或复制该函数时遇到了问题。structuredClone
不能复制不可序列化的值,比如函数或 DOM 节点。
6.3.2 无法克隆DOM元素
1 2 3 4
| const test = document.getElementById('test');
const testClone = structuredClone(test); console.log(testClone);
|

6.3.3 不保留RegExp
的lastIndex
属性
虽然不会去克隆正则表达式
,但这是值得注意的一点:
1 2 3 4 5 6 7 8
| const regex = /beauty/g; const str = 'Coding Beauty: JS problems are solved at Coding Beauty';
console.log(regex.index); console.log(regex.lastIndex);
const regexClone = structuredClone(regex); console.log(regexClone.lastIndex);
|
6.4 其他的限制
了解这些限制很重要,以避免使用该函数时出现意外行为。
部分克隆,部分移动
这是一个更复杂的情况。
你将内部对象从源对象转移到克隆对象,而不是复制。
这意味着源对象中没有留下任何可以改变的东西:
1 2 3 4 5 6 7 8 9 10
| const uInt8Array = Uint8Array.from( { length: 1024 * 1024 * 16 }, (v, i) => i );
const transferred = structuredClone(uInt8Array, { transfer: [uInt8Array.buffer], });
console.log(uInt8Array.byteLength);
|
总的来说,structuredClone()
是JavaScript开发者工具箱中的一个宝贵补充,使对象克隆比以往任何时候都更容易。