文章目录
- [JavaScript 中浅拷贝与深拷贝的差异与实现方式整理](#JavaScript 中浅拷贝与深拷贝的差异与实现方式整理)
-
- 1.手写浅拷贝(shallowcopy)
- [2.手写深拷贝(deep copy)](#2.手写深拷贝(deep copy))
JavaScript 中浅拷贝与深拷贝的差异与实现方式整理
浅拷贝是指复制值时,满足对象A 与物件B 不同,但物件A 与物件B 有相同的属性,并且属性的原型链相同。
而深拷贝则是指在拷贝时,对象A 与对象B 不同,两者在原型链上仅是结构相同,但其属性实际的地址不同。在拷贝值时,有可能会遇到变数是多层的情境,例如是一个对象里还有对象,深拷贝的定义会是每一层的值都不会共享址(reference)。
这样听起来可能比较抽象,具体来说,以lodash 这个套件提供的效用函式为例,有分成clone和cloneDeep两种不同效用函式,clone只用于浅拷贝(第一层拷贝),但cloneDeep可用于深拷贝。下面的例子说明两者的区别:
javascript
// lodash 的浅拷贝 clone
var objects = [{ a: 1 }, { b: 2 }];
var shallow = _.clone(objects);
console.log(objects === shallow); // false
console.log(shallow[0] === objects[0]); // true
// lodash 的深拷贝 cloneDeep
var objects = [{ a: 1 }, { b: 2 }];
var deep = _.cloneDeep(objects);
console.log(objects === deep); // false
console.log(deep[0] === objects[0]); // false
1.手写浅拷贝(shallowcopy)
方法一:手动复制值
javascript
let objA = {
a: 1,
b: { c: 3 },
};
let objB = { a: objA.a, b: objA.b };
console.log(objA === objB); // false
console.log(objA.b === objB.b); // true, 第二层的对象是指向相同位置
方法二:使用展开参数
javascript
let objA = {
a: 1,
b: { c: 3 },
};
let objB = { ...objA };
console.log(objA === objB); // false
console.log(objA.a === objB.a); // true, 第二层的对象是指向相同位置
console.log(objA.b === objB.b); // true, 第二层的对象是指向相同位置
方法三:使用Object.assign
javascript
let objA = {
a: 1,
b: { c: 3 },
};
let objB = Object.assign({}, objA);
console.log(objA === objB); // false
console.log(objA.b === objB.b); // true, 第二层的对象是指向相同位置
2.手写深拷贝(deep copy)
方法一:使用JSON.parse(JSON.stringify(...))
前置知识:
1.全局内建工具函数
JSON 是 JS 全局内建工具对象,类似于 Math
2.序列化
序列化(serialization)
就是把内存里的复杂数据结构
转换成一种 "可以存、可以传、可以写成字符串的形式"
例如内存中的对象:
javascript
const obj = {
name: 'Tom',
age: 18,
hobbies: ['code', 'music']
};
序列化后:
javascript
{"name":"Tom","age":18,"hobbies":["code","music"]}
3.有些数据类型不能被序列化,会丢失数据
| 类型 | 结果 |
|---|---|
| function | 丢失 |
| undefined | 丢失 |
| Symbol | 丢失 |
| Date | 变字符串 |
| Map / Set | 变空对象 |
| 循环引用 | 直接报错 |
这个做法是先将对象用JSON.stringify序列化为string,再通过JSON.parse转换回物件。要特别注意,这做法只能用于可序列化的物件,有些无法序列化的物件例如:function、HTML 的元素,这些是无法序列化的,所以执行前,需要先确认是否可以序列化,否则在执行JSON.stringify时会失败。
javascript
let objA = {
a: 1,
b: { c: 3 },
};
function deepCopy(item) {
return JSON.parse(JSON.stringify(item));
}
let objB = deepCopy(objA);
console.log(objA === objB); // false
console.log(objA.b === objB.b); // false
方法二:使用structuredClone(value)
针对可序列化的物件,有另外一种透过JavaScript 内建的方法达成深拷贝。这种方法是structuredClone(value),用法如下。
javascript
let objA = {
a: 1,
b: { c: 3 },
};
let objB = structuredClone(objA);
console.log(objA === objB); // false
console.log(objA.b === objB.b); // false
方法三:手动递归式深拷贝
初级版本:适合面试
javascript
//目的:深拷贝,不共用地址,自己用自己的地址,确保新旧对象之间完全独立
// 递归出口
//如果不是对象数组,就直接返回,直接拿到对应的值
//如果是对象数组,就可能是嵌套的结构,就需要递归遍历,处理每一层嵌套
// 遍历
// 循环当前对象或者数组中的每一项,依次赋值返回
function deepClone(obj)
{
// 递归出口
if(typeof obj===null || typeof obj !== 'object') return obj;
const clone =Array.isArray?[] :{}
for(let key in obj)
{
clone[key]=deepClone(obj[key])
}
return clone
}
const original = {
name: "Alice",
age: 25,
info: { //information 的简写
city: "Beijing"
}
};
var deep=deepClone(original);
deep.info.city="xian";
console.log(original.info.city); //Beijing
console.log(deep.info.city);// xian
解析:
- typeof 数组 对象 null 的时候结果都是 object 所以这部分要考虑
- 区分数组对象的原因是是因为有些方法只有数组能用,给他拷贝成了对象就用不了
拓展:
这个版本能处理普通对象和数组,但不支持循环引用和特殊对象