浅拷贝 (Shallow Copy) 与深拷贝 (Deep Copy)
Node.js 浅拷贝 (Shallow Copy) 与深拷贝 (Deep Copy)
资料传递方式
call by value 传值
- String
- Number
- Boolean
- Null
- Undefined
- Symbol
当複製变数时,会将变数的 数值
複製到新的变数,複製的变数被修改,不会
影响到原始变数
let Employee = 'Kay';
let directCopyEmployee = Employee;
directCopyEmployee = 'Jay';
// Kay
console.log(Employee);
// Jay
console.log(directCopyEmployee);
call by reference 传址
- Object
- Array
- Function
当複製变数时,会将变数的 reference 位址
指定到新的变数,複製的变数被修改,会
影响到原始变数
let Employee = {
name: 'Kay',
age: 17
};
let directCopyEmployee = Employee;
directCopyEmployee.name = 'Jay';
// { name: 'Jay', age: 17 }
console.log(Employee);
// { name: 'Jay', age: 17 }
console.log(directCopyEmployee);
什麽是浅拷贝 (Shallow Copy) 与深拷贝 (Deep Copy)?
浅拷贝 (Shallow Copy)
- 只能完成
第一层
的浅层複製,若有第二层结构时,还是依据referene 参考特性
处理,也就代表指向记忆体位址还是一样的。
深拷贝 (Deep Copy)
- 完整深度複製指定物件,包含所有层及的资料
- 操作新物件不影响原物件,两者指向不同记忆体位址。
浅拷贝 (Shallow Copy) 方法
- 物件有 2 层以上结构,使用浅拷贝,改变第 2 层资料,
会影响
原有物件
Array 阵列
使用 Object.assign() 複製
let Employee = ['Kay', 'Jay', () => {}];
let directCopyEmployee = Employee;
let shallowCopyEmployee = Object.assign([], Employee);
directCopyEmployee[1] = 'KJ';
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'KJ', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'KJ', [Function (anonymous)] ]
console.log(directCopyEmployee);
// [ 'Kay', 'Shallow', [Function (anonymous)] ]
console.log(shallowCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === shallowCopyEmployee);
物件有 2 层以上结构,使用浅拷贝,改变第 2 层资料,会影响
原有物件
let Employee = [
'Kay',
'Jay',
['Apple', 'Pen']
];
let directCopyEmployee = Employee;
let shallowCopyEmployee = Object.assign([], Employee);
directCopyEmployee[1] = 'KJ';
// 改变第 1 层资料,不影响原有物件
shallowCopyEmployee[1] = 'Shallow';
// 改变第 2 层资料,会影响原有物件
shallowCopyEmployee[2][0] = 'Pineapple';
// [ 'Kay', 'KJ', [ 'Pineapple', 'Pen' ] ]
console.log(Employee);
// [ 'Kay', 'KJ', [ 'Pineapple', 'Pen' ] ]
console.log(directCopyEmployee);
// [ 'Kay', 'Shallow', [ 'Pineapple', 'Pen' ] ]
console.log(shallowCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === shallowCopyEmployee);
// false: 第一层数值资料完整複製
console.log(Employee[1] === shallowCopyEmployee[1]);
// true: 第二层 reference 原始物件第 2 层
console.log(Employee[2] === shallowCopyEmployee[2]);
使用 Array.concat() 複製
let Employee = ['Kay', 'Jay', () => {}];
let shallowCopyEmployee = Employee.concat();
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'Jay', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'Shallow', [Function (anonymous)] ]
console.log(shallowCopyEmployee);
使用 Array.slice() 複製
let Employee = ['Kay', 'Jay', () => {}];
let shallowCopyEmployee = Employee.slice();
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'Jay', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'Shallow', [Function (anonymous)] ]
console.log(shallowCopyEmployee);
使用 Array.map() 複製
let Employee = ['Kay', 'Jay', () => {}];
let shallowCopyEmployee = Employee.map(x => x);
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'Jay', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'Shallow', [Function (anonymous)] ]
console.log(shallowCopyEmployee);
使用 Array.filter() 複製
let Employee = ['Kay', 'Jay', () => {}];
let shallowCopyEmployee = Employee.filter(() => true);
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'Jay', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'Shallow', [Function (anonymous)] ]
console.log(shallowCopyEmployee);
使用 Array.reduce() 複製
let Employee = ['Kay', 'Jay', () => {}];
let shallowCopyEmployee = Employee.reduce((newArray, element) => {
newArray.push(element);
return newArray;
}, []);
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'Jay', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'Shallow', [Function (anonymous)] ]
console.log(shallowCopyEmployee);
使用 Array.from() 複製
let Employee = ['Kay', 'Jay', () => {}];
let shallowCopyEmployee = Array.from(Employee);
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'Jay', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'Shallow', [Function (anonymous)] ]
console.log(shallowCopyEmployee);
使用 […Array] 展开运算元複製
let Employee = ['Kay', 'Jay', () => {}];
let shallowCopyEmployee = [...Employee];
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'Jay', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'Shallow', [Function (anonymous)] ]
console.log(shallowCopyEmployee);
使用 JSON.stringify() 及 JSON.parse() 複製
注意!
函数
不可以被拷贝
let Employee = ['Kay', 'Jay', () => {}];
let shallowCopyEmployee = JSON.parse( JSON.stringify(Employee));
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'Jay', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'Shallow', null ]
console.log(shallowCopyEmployee);
自製浅拷贝函式
let customShallowCopy = function (OriginalObject) {
// 只拷贝物件
if (typeof OriginalObject !== 'object') {
return OriginalObject;
}
// 根据 OriginalObject 的类型,判断要新建一个阵列 or 一个物件
let NewObject = (OriginalObject instanceof Array) ? [] : {};
// 遍历 OriginalObject,并且判断是 OriginalObject 的属性才拷贝
for (let key in OriginalObject) {
if (OriginalObject.hasOwnProperty(key)) {
// 如果是物件自己的属性,複製资料
NewObject[key] = OriginalObject[key];
}
}
return NewObject;
}
let Employee = ['Kay', 'Jay', () => {}];
let shallowCopyEmployee = customShallowCopy(Employee);
shallowCopyEmployee[1] = 'Shallow';
// [ 'Kay', 'Jay', [Function (anonymous)] ]
console.log(Employee);
// [ 'Kay', 'Shallow', null ]
console.log(shallowCopyEmployee);
Object 物件
使用 Object.assign() 複製
let Employee = {
name: 'Kay',
age: 17,
sayHi : () => {}
};
let directCopyEmployee = Employee;
let shallowCopyEmployee = Object.assign({}, Employee);
directCopyEmployee.name = 'Jay';
shallowCopyEmployee.name = 'Shallow';
// { name: 'Jay', age: 17, sayHi: [Function: sayHi] }
console.log(Employee);
// { name: 'Jay', age: 17, sayHi: [Function: sayHi] }
console.log(directCopyEmployee);
// { name: 'Shallow', age: 17, sayHi: [Function: sayHi] }
console.log(shallowCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === shallowCopyEmployee);
物件有 2 层以上结构,使用浅拷贝,改变第 2 层资料,会影响
原有物件
let Employee = {
name: 'Kay',
age: 17,
score : {
math : 30,
coding : 70,
},
};
let directCopyEmployee = Employee;
let shallowCopyEmployee = Object.assign({}, Employee)
directCopyEmployee.name = 'Jay';
// 改变第 1 层资料,不影响原有物件
shallowCopyEmployee.name = 'Shallow';
// 改变第 2 层资料,会影响原有物件
shallowCopyEmployee.score.math = 99999;
// {
// name: 'Jay',
// age: 17,
// score: { math: 99999, coding: 70 }
// }
console.log(Employee);
// {
// name: 'Jay',
// age: 17,
// score: { math: 99999, coding: 70 }
// }
console.log(directCopyEmployee);
// {
// name: 'Shallow',
// age: 17,
// score: { math: 99999, coding: 70 }
// }
console.log(shallowCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === shallowCopyEmployee);
// true: 第二层 reference 原始物件第 2 层
console.log(Employee.score === shallowCopyEmployee.score);
使用 {…Object} 展开运算元複製
let Employee = {
name: 'Kay',
age: 17,
sayHi : () => {}
};
let directCopyEmployee = Employee;
let shallowCopyEmployee = {...Employee};
directCopyEmployee.name = 'Jay';
shallowCopyEmployee.name = 'Shallow';
// { name: 'Jay', age: 17, sayHi: [Function: sayHi] }
console.log(Employee);
// { name: 'Jay', age: 17, sayHi: [Function: sayHi] }
console.log(directCopyEmployee);
// { name: 'Shallow', age: 17, sayHi: [Function: sayHi] }
console.log(shallowCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === shallowCopyEmployee);
使用 JSON.stringify() 及 JSON.parse() 複製
注意!
函数
不可以被拷贝
let Employee = {
name: 'Kay',
age: 17,
sayHi : () => {}
};
let directCopyEmployee = Employee;
let shallowCopyEmployee = JSON.parse( JSON.stringify(Employee));
directCopyEmployee.name = 'Jay';
shallowCopyEmployee.name = 'Shallow';
// { name: 'Jay', age: 17, sayHi: [Function: sayHi] }
console.log(Employee);
// { name: 'Jay', age: 17, sayHi: [Function: sayHi] }
console.log(directCopyEmployee);
// { name: 'Shallow', age: 17 }
console.log(shallowCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === shallowCopyEmployee);
自製浅拷贝函式
let customShallowCopy = function (OriginalObject) {
// 只拷贝物件
if (typeof OriginalObject !== 'object') {
return OriginalObject;
}
// 根据 OriginalObject 的类型,判断要新建一个阵列 or 一个物件
let NewObject = (OriginalObject instanceof Array) ? [] : {};
// 遍历 OriginalObject,并且判断是 OriginalObject 的属性才拷贝
for (let key in OriginalObject) {
if (OriginalObject.hasOwnProperty(key)) {
// 如果是物件自己的属性,複製资料
NewObject[key] = OriginalObject[key];
}
}
return NewObject;
}
let Employee = {
name: 'Kay',
age: 17,
sayHi : () => {}
};
let directCopyEmployee = Employee;
let shallowCopyEmployee = customShallowCopy(Employee);
directCopyEmployee.name = 'Jay';
shallowCopyEmployee.name = 'Shallow';
// { name: 'Jay', age: 17, sayHi: [Function: sayHi] }
console.log(Employee);
// { name: 'Jay', age: 17, sayHi: [Function: sayHi] }
console.log(directCopyEmployee);
// { name: 'Shallow', age: 17, sayHi: [Function: sayHi] }
console.log(shallowCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === shallowCopyEmployee);
深拷贝 (Deep Copy) 方法
阵列 Array
自製深拷贝函式
当遇到多层的资料,会再各别进入该层进行 浅拷贝
let customDeepCopy = function (OriginalObject) {
// 只拷贝物件
if (typeof OriginalObject !== 'object') {
return OriginalObject;
}
// 根据 OriginalObject 的类型,判断要新建一个阵列 or 一个物件
let NewObject = (OriginalObject instanceof Array) ? [] : {};
// 遍历 OriginalObject,并且判断是 OriginalObject 的属性才拷贝
for (let key in OriginalObject) {
if (OriginalObject.hasOwnProperty(key)) {
if (typeof OriginalObject[key] === 'object') {
// 如果 OriginalObject 的子属性是物件,则再次深拷贝
NewObject[key] = customDeepCopy(OriginalObject[key]);
} else {
// 不是物件,直接拷贝
NewObject[key] = OriginalObject[key];
}
}
}
return NewObject;
}
let Employee = [
'Kay',
'Jay',
['Apple', 'Pen']
];
let directCopyEmployee = Employee;
let deepCopyEmployee = customDeepCopy(Employee);
directCopyEmployee[1] = 'KJ';
// 改变第 1 层资料,不影响原有物件
deepCopyEmployee[1] = 'Shallow';
// 改变第 2 层资料,会影响原有物件
deepCopyEmployee[2][0] = 'Pineapple';
// [ 'Kay', 'KJ', [ 'Apple', 'Pen' ] ]
console.log(Employee);
// [ 'Kay', 'KJ', [ 'Apple', 'Pen' ] ]
console.log(directCopyEmployee);
// [ 'Kay', 'Shallow', [ 'Pineapple', 'Pen' ] ]
console.log(deepCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === deepCopyEmployee);
// false: 第一层数值资料完整複製
console.log(Employee[1] === deepCopyEmployee[1]);
// false: 第 2 层建立全新物件
console.log(Employee[2] === deepCopyEmployee[2]);
使用 lodash 套件 cloneDeep()
const _ = require('lodash');
let Employee = [
'Kay',
'Jay',
['Apple', 'Pen']
];
let directCopyEmployee = Employee;
let deepCopyEmployee = _.cloneDeep(Employee);
directCopyEmployee[1] = 'KJ';
// 改变第 1 层资料,不影响原有物件
deepCopyEmployee[1] = 'Shallow';
// 改变第 2 层资料,不影响原有物件
deepCopyEmployee[2][0] = 'Pineapple';
// [ 'Kay', 'KJ', [ 'Apple', 'Pen' ] ]
console.log(Employee);
// [ 'Kay', 'KJ', [ 'Apple', 'Pen' ] ]
console.log(directCopyEmployee);
// [ 'Kay', 'Shallow', [ 'Pineapple', 'Pen' ] ]
console.log(deepCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === deepCopyEmployee);
// false: 第一层数值资料完整複製
console.log(Employee[1] === deepCopyEmployee[1]);
// false: 第 2 层建立全新物件
console.log(Employee[2] === deepCopyEmployee[2]);
物件 Object
自製深拷贝函式
当遇到多层的资料,会再各别进入该层进行 浅拷贝
let customDeepCopy = function (OriginalObject) {
// 只拷贝物件
if (typeof OriginalObject !== 'object') {
return OriginalObject;
}
// 根据 OriginalObject 的类型,判断要新建一个阵列 or 一个物件
let NewObject = (OriginalObject instanceof Array) ? [] : {};
// 遍历 OriginalObject,并且判断是 OriginalObject 的属性才拷贝
for (let key in OriginalObject) {
if (OriginalObject.hasOwnProperty(key)) {
if (typeof OriginalObject[key] === 'object') {
// 如果 OriginalObject 的子属性是物件,则再次深拷贝
NewObject[key] = customDeepCopy(OriginalObject[key]);
} else {
// 直接拷贝
NewObject[key] = OriginalObject[key];
}
}
}
return NewObject;
}
let Employee = {
name: 'Kay',
age: 17,
score : {
math : 30,
coding : 70,
},
};
let directCopyEmployee = Employee;
let deepCopyEmployee = customDeepCopy(Employee);
directCopyEmployee.name = 'Jay';
// 改变第 1 层资料,不影响原有物件
deepCopyEmployee.name = 'Shallow';
// 改变第 2 层资料,不影响原有物件
deepCopyEmployee.score.math = 99999;
// { name: 'Jay', age: 17, score: { math: 30, coding: 70 } }
console.log(Employee);
// { name: 'Jay', age: 17, score: { math: 30, coding: 70 } }
console.log(directCopyEmployee);
// { name: 'Shallow', age: 17, score: { math: 99999, coding: 70 } }
console.log(deepCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === deepCopyEmployee);
// false: 第 2 层建立全新物件
console.log(Employee.score === deepCopyEmployee.score);
使用 lodash 套件 cloneDeep()
const _ = require('lodash');
let Employee = {
name: 'Kay',
age: 17,
score : {
math : 30,
coding : 70,
},
};
let directCopyEmployee = Employee;
let deepCopyEmployee = _.cloneDeep(Employee)
directCopyEmployee.name = 'Jay';
// 改变第 1 层资料,不影响原有物件
deepCopyEmployee.name = 'Shallow';
// 改变第 2 层资料,不影响原有物件
deepCopyEmployee.score.math = 99999;
// { name: 'Jay', age: 17, score: { math: 30, coding: 70 } }
console.log(Employee);
// { name: 'Jay', age: 17, score: { math: 30, coding: 70 } }
console.log(directCopyEmployee);
// { name: 'Shallow', age: 17, score: { math: 99999, coding: 70 } }
console.log(deepCopyEmployee);
// true: reference 原始物件
console.log(Employee === directCopyEmployee);
// false: 建立全新物件
console.log(Employee === deepCopyEmployee);
// false: 第 2 层建立全新物件
console.log(Employee.score === deepCopyEmployee.score);
类别 class
自製 clone() 方法
class Cat {
constructor(name, color){
this.name = name;
this.color = color;
}
sayHi() {
console.log(`[Cat] My name is「${this.name}」 My color is 「${this.color}」`);
}
clone() {
return new Cat(this.name, this.color);
}
}
let KittyCat = new Cat('Kay', 'pink');
directCopyKittyCat = KittyCat;
deepCopyKittyCat = KittyCat.clone();
directCopyKittyCat.name = 'Jay';
deepCopyKittyCat.color = 'yellow';
// Cat { name: 'Jay', color: 'pink' }
console.log(KittyCat);
// Cat { name: 'Jay', color: 'pink' }
console.log(directCopyKittyCat);
// Cat { name: 'Kay', color: 'yellow' }
console.log(deepCopyKittyCat);
JSON.stringify() 及 JSON.parse() 複製产生的问题
- 函数 : 会连同 key 一起消失。
- undefined : 会连同 key 一起消失。
- NaN : 会被转成 null。
- Infinity :会被转成 null。
- regExp : 会被转成 空 {}。
- Date : 型别会由 Data 转成 string。
const originalData = {
// 资料会完全不见
undefined: undefined,
// 会被强制转换成 null
notANumber: NaN,
// 会被强制转换成 null
infinity: Infinity,
// 会强制转换成空物件 {}
regExp: /.*/,
// 会变成字串
date: new Date('2022-01-01T23:59:59'),
// 资料会完全不见
sayHi : () => {}
};
const faultyClonedData = JSON.parse(JSON.stringify(originalData));
// {
// undefined: undefined,
// notANumber: NaN,
// infinity: Infinity,
// regExp: /.*/,
// date: 2022-01-01T15:59:59.000Z,
// sayHi: [Function: sayHi]
// }
console.log(originalData);
// {
// notANumber: null,
// infinity: null,
// regExp: {},
// date: '2022-01-01T15:59:59.000Z'
// }
console.log(faultyClonedData);
拷贝效能比较
效能比较原始测试文
- 测试时间:2020-04-30
- 测试平台
- Chrome v81.0
- Safari v13.1
- Firefox v75.0
- 作业系统:MacOs High Sierra v10.13.6.
拷贝方法
浅拷贝 Shallow Copy
编号 | 方法 |
---|---|
A | {...object} |
B | Object.assign() |
C | Object.key().reduce() |
D | Object.defineProperties() |
E | jQuery.extend({}, obj) |
F | lodash _.clone(obj) |
G | lodash _.clone(obj, true) |
H | lodash _.extend() |
I | customShallowCopy() |
深拷贝 Deep Copy
编号 | 方法 |
---|---|
J | lodash _.cloneDeep() |
K | JSON.parse() & JSON.stringify() |
L | jQuery.extend(true, {}, obj) |
M | obj.conttructor() |
N | EClone() |
O | obj.conttructor() |
P | Object.getOwnPropertyDescriptor() |
Q | handleDateArrayObject() |
R | __getDeepCircularCopy__ |
S | WeakMapCache() |
T | removeUniqueId |
U | copyPropDescs() |
拷贝方法 Benchmark 测试结果
浅拷贝 Shallow Copy 测试结果
方法 | 测试结果 |
---|---|
方法 A {…object} | 在 Chrome 及 Firefox 执行速度最快,在 Safari 执行速度中等 |
方法 B Object.assign() | 在所有浏览器执行速度相对都是较快 |
方法 E jQuery 及 方法 F,G,H lodash | 执行速度中等偏快 |
方法 K JSON.parse/stringify | 相当慢 |
方法 D, U | 在所有浏览器都很慢 |
深拷贝 Deep Copy 测试结果
方法 | 测试结果 |
---|---|
方法 Q | 在所有浏览器中都是最快的 |
方法 L jQuery 及方法 J lodash | 中等速度 |
方法 K JSON.parse/stringify | 相当慢 |
方法 U | 在所有浏览器都是最慢 |
方法 J lodash 及 方法 U | 当物件阶层超过 1000 时,在 Chrome 会当掉 |
拷贝方法测试原始码
参考资料
- JS 深拷贝与浅拷贝 | Explosion Units
- JavaScript 浅拷贝 (Shallow Copy) 与深拷贝 (Deep Copy) | Roya’s Blog
- JS 中的浅拷贝 (Shallow copy) 与深拷贝 (Deep copy) 原理与实作
- 关于JS中的浅拷贝(shallow copy)以及深拷贝(deep copy) | by Andy Chen | Andy的技术分享blog | Medium
- How do I correctly clone a JavaScript object? - Stack Overflow
- 透过複製阵列理解 JS 的浅拷贝与深拷贝 - JavaScript - Askie’s Coding Life