什麽是拷贝? 今天朋友想 copy 你的报告,最简单的就是影印一份给他,但是当你修改报告中的内容时,发现朋友拷贝的那份也跟着修改了,哪尼,难道我见证了量子纠缠?!
量子的世界请去找老高,但在 JavaScript 中,这个称为 浅拷贝 的现象。
深拷贝与浅拷贝的概念像以下图示:
浅拷贝:复制过後部分 A、B 的值还是会互相影响
深拷贝:复制过後 A、B 是完全的独立个体,彼此值不影响
为什麽会有这两种拷贝差异呢?
背後原理要先带到 call by value 与 call by sharing 观念。
关於这部分有很多大神的文章可以拜读,这里简单统整一下结论,JavaScript 中变数分为两种型别:
基本型别:复制变数时,记忆体中会新增一个拥有同样值的新记忆体位置,呼叫变数会丢出在记忆体中存取的值,称为 call by value。
物件型别:复制物件型别的变数时,实际上是复制记忆体中的地址,相同的记忆体位置指向一样的值; 对物件赋予新的值,则记忆体存的值也会跟着变动为新位置的地址,称为 call by sharing。
原始型别没有深浅拷贝之分,主要是物件型别的变数因记忆体存址指向的关系,浅拷贝指向的记忆体位置相同,而深拷贝指向不同的记忆体位置,因此有修改资料会不会影响的差异。
接下来透过程序码,针对不同的物件拷贝行为来看看是属於浅拷贝还是深拷贝!
举例: A、B、C 三人都点了拿铁,但在尺寸及其他项目上有不同的要求
使用一般的 =
赋值方式
let A = {coffee: 'Latte', size: 'L'};
let B = A; // A 赋值给 B
B.size = 'M' // 修改 B 的资料
console.log(B) // {coffee: 'Latte', size: 'M'}
console.log(A) // {coffee: 'Latte', size: 'M'},A咖啡尺寸从 L 变成 M
Object.assign
可以复制物件中的资料到另一个物件上,当物件资料只有一层时,可以做到资料不互相影响,但若结构来到两层以上时,资料还是会受彼此影响,依然属於浅拷贝。
let A = {coffee: 'Latte', size: 'L'};
let B = Object.assign({}, A);
B.size = 'M' // b 更改尺寸为 M
console.log(B) // {coffee: 'Latte', size: 'M'}
console.log(A) // {coffee: 'Latte', size: 'L'},A 资料不受影响
// B 复制给 C
B.others = {sugar: 'less', shot: 3}
let C = Object.assign({},B)
C.size = 'S'
C.others.sugar = 'double'
C.others.shot = 1
console.log(C) // {coffee: 'Latte', size: 'S', others: {sugar: 'double', shot: 1}}
console.log(B) // {coffee: 'Latte', size: 'M', others: {sugar: 'double', shot: 1}}
// B 的 size 属於第一层资料,不受 C 影响,但是第二层的 others 资料会跟着变动
使用 展开运算子 spread operator ...
, 将物件的值存入另一个物件中,但跟 Object.assign
一样问题,当资料来到两层以上时还是浅拷贝。
let A = {coffee: 'Latte', size: 'L'};
let B = {...A}
B.size = 'M' // 修改 B 的内容
B.others = {sugar: 'less', shot:3} // 增加 B 的内容
console.log(A) // {coffee: 'Latte', size: 'L'},不受影响
console.log(B) // {coffee: 'Latte', size: 'M', others: {sugar: 'less', shot: 1}}
// 将有两层资料的 B 同样以解构赋值方式拷贝给 C
let C = {...B}
C.size = 'S' // 修改 C 内容
C.others.sugar = 'less'
C.others.shot = 1
console.log(C) // {coffee: 'Latte', size: 'S', others: {sugar: 'less', shot: 1}}
console.log(B) // {coffee: 'Latte', size: 'M', others: {sugar: 'less', shot: 1}}
// B 的 size 属於第一层资料,不受 C 影响,但是第二层的 others 资料会跟着变动
好的,上述的展开运算子和 Object.assign
顶多可以做到一层资料的完全拷贝(如: A、B),但在巢状的资料结构,如 B、C 两人的咖啡资料怎麽做到深拷贝呢? 透过 JSON
格式!
JSON.stringify
把物件转成字串,再用 JSON.parse
把字串转回为物件。
这是真正可以做到深拷贝的方法,但是仅限 JSON 格式!
// A、B 的拷贝没问题了,直接从 B、C 拷贝试试
let B = {
coffee: 'Latte',
size: 'L',
others: {sugar: 'less', shot: 3}
};
let C = JSON.parse(JSON.stringify(B))
C.size = 'S'
C.others.sugar = 'double'
C.others.shot = 1
console.log(B) // {coffee: 'Latte', size: 'L',others: {sugar: 'less', shot: 3}};
console.log(C) // {coffee: 'Latte', size: 'S',others: {sugar: 'double', shot: 1}};
// C 修改的内容并没有影响到 B,这才做到真正的深拷贝
How to differentiate between deep and shallow copies in JavaScript
深入探讨 JavaScript 中的参数传递:call by value 还是 reference?
JS 变数传递探讨:pass by value 、 pass by reference 还是 pass by sharing?
JavaScript 浅拷贝 (Shallow Copy) 与深拷贝 (Deep Copy)
<<: Day20 :【TypeScript 学起来】是 JavaScript 没有的 Function Overloads(函式超载)
>>: 【从实作学习ASP.NET Core】Day23 | 前台 | Session 购物车 (1)
Genero FGL为一个出自於资料库的语言,但怎麽和资料库搭上边的,我们还是需要来做一下理解。 ...
函式解构: functionName:为函式命名,当函式型态为函式陈述式,则必须命名。 函式里面宣告...
这一个章节节我们要来介绍复合查询,当单一的查询子句无法完成需求时,为了应付这种高级查询需求,所以就产...
Colab连结 昨天我们使用了降低多数样本 Undersampling 的方式来解决少数样本的问题,...
物件复制: 浅层复制(shallow copy):仅被复制的一方能保留第一层的物件之值,但是当复制方...