浅拷贝与深拷贝
- 前言
- 一、浅拷贝?
- 1.1是什么?
- 1.2做什么?
- 1.3为什么使用?
- 1.4实现方式?
- 1.5 应用场景?
- 二、深拷贝?
- 2.1是什么?
- 2.2做什么?
- 2.3为什么使用?
- 2.4实现方式?
- 2.5应用场景?
- 三、区别
- 总结
前言
我们对数组或者对象进行操作的时候,有时并不想操作原始对象,就会将对象或数组复制出新一份,在新的一份中进行操作。
深拷贝与浅拷贝是常用于对象或数组进行复制。
一、浅拷贝?
1.1是什么?
浅拷贝是指复制对象时,只复制对象的引用,而不复制引用指向的对象本身。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存中的地址。
先用一张图理解这句话
当B从A中复制一份时,他们在内存地址中指向仍旧是同一个,如果此时A改变了某些值,因为B和A指向是相同的,B此时也会跟着改变。也就是说B只是复制A在内存地址的引用,并没复制A的本身。
1.2做什么?
浅拷贝它复制了原始对象中的所有字段到新的对象中,对于值类型,复制操作不会对原对象的字段值有任何影响;
对于引用类型,新的对象对引用类型中的字段赋值等操作,会对原引用类型对象中的字段进行更改。
浅拷贝经常用于在不需要深度复制对象的情况下快速复制对象。
1.3为什么使用?
- 内存使用优化:浅拷贝允许多个对象共享相同的数据区,这样可以显著减少内存的消耗。特别是当处理大型数据结构。
- 性能提升:减少拷贝的次数,从而提升程序的运行效率。
- 特定需求:只要复制对象的顶层数据,而不是对象的所有层次。
1.4实现方式?
- 扩展运算符(…)
const originalArray = [1, 2, [3, 4]];
const shallowCopyArray = [...originalArray];
- Object.assign()
const originalArray = { a: 1, b: { c: 2 } };
const shallowCopyArray = Object.assign({},originalArray);
- Array.prototype.slice(), Array.prototype.concat()
const originalArray = [1, 2, [3, 4]];
const shallowCopyArray = originalArray.slice();
const shallowCopyArray = originalArray.concat();
1.5 应用场景?
- 组件间传值:子组件只是读取这些数据而不进行修改
- 计算属性或方法中的临时变量:基于当前组件的状态或传入的参数创建一些临时变量。如果这些临时变量只是简单地读取状态或参数的值,而不进行修改
二、深拷贝?
2.1是什么?
深拷贝是指复制对象时,不仅复制对象本身,还复制对象所引用的所有对象,直到所有引用的对象都被完全复制。
先用一张图理解这句话
当B从A中复制一份时,不仅新创建了B对象,还创造一块新的内存,此时B和A在内存中的指向不再是同一块。如果A改变了,B不会受影响。
2.2做什么?
深拷贝独立创建新的对象和开辟一块新的内存,对于值和引用类型,复制操作不会对原对象的字段值有任何影响;
深拷贝经常用于不影响原对象情况下复制新对象。深拷贝相对于浅拷贝来说,更加复杂耗时,但可以确保数据的安全性。
2.3为什么使用?
- 避免数据冲突:使用深拷贝可以确保新对象与原始对象完全独立,对其中一个对象的修改不会影响另一个。
- 确保数据完整:深拷贝可以复制对象的所有层级,从而确保整个对象的完整性。
- 特定需求:不影响原始数据操作。
2.4实现方式?
- JSON.stringify()
const obj2=JSON.parse(JSON.stringify(obj1));
但是这种方式存在弊端,会忽略undefined、symbol和函数
const obj = {
name: 'A',
name1: undefined,
name3: function() {},
name4: Symbol('A')
}
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2); // {name: "A"}
- 循环递归
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
- _.cloneDeep()
const _ = require('lodash');
const obj = {
a: 1,
};
const obj1 = _.cloneDeep(obj);
2.5应用场景?
-
组件间数据传递:子组件对数据的修改不会影响到父组件或其他兄弟组件。
-
Vuex状态管理:在Vuex中,状态是响应式的,当状态改变时,视图也会自动更新。通过深拷贝,我们可以创建一个状态的独立副本,在组件内部进行修改,而不会触发全局状态的更新。
-
避免直接修改props:需要基于props的数据进行修改,可以先使用深拷贝创建一个副本,然后在副本上进行修改。
三、区别
- 共同点:
- 都是复制数据
- 不同点:
- 浅拷贝是复制内存中的地址,拷贝前后的对象,因为引用类型共享了同一块内存,修改会相互影响。基本类型值不变,引用类型会改变
- 深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址。基本类型和引用类型不改变
总结
- 浅拷贝:
- 引用同一块内存,会影响原数据
- 基本类型值不变,引用类型会改变
- 实现方式:… , Object.assign() , slice(),concat()
- 深拷贝:
- 新开辟内存,不影响原数据
- 基本类型和引用类型不改变
- 实现方式:循环递归 , JSON.stringify() , loadsh库中_.cloneDeep()