深入理解JavaScript中数据拷贝机制——深拷贝与浅拷贝

367 阅读4分钟

引言

在JavaScript的学习过程中,数据的拷贝机制一直都是一个很重要的知识点。前端面试中,深浅拷贝一直都是一个很频繁的问题,下面我们一起全面总结学习下JavaScript的数据拷贝机制,带你一文深入掌握深拷贝与浅拷贝。

浅拷贝(Shallow Copy)

浅拷贝指的是复制对象的第一层属性,如果属性是基本数据类型,则直接复制其值;但如果属性是引用类型(如对象、数组),则复制的是内存地址的引用,而不是实际的值。

手搓浅拷贝

function shallowCopy(obj) {
    if (typeof obj !== 'object' || obj === null) {
        return obj; // 如果是基本数据类型,直接返回
    }

    let copy = Array.isArray(obj) ? [] : {}; // 区分数组和对象

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            copy[key] = obj[key]; // 只复制第一层
        }
    }
    return copy;
}

Object.assign()

Object.assign(target, ...sources) 用于将一个或多个源对象(source)的可枚举属性(包括自身的字符串键属性,但不包括继承属性和 Symbol 属性)拷贝到目标对象(target),并返回目标对象。

const objNew = Object.assign({},obj);

Array.prototype.concat()

concat() 方法用合并两个或多个数组,并返回一个新数组,不会改变原数组。 如果 concat() 处理的是引用类型(对象、数组),它会复制引用,而不会真正拷贝对象本身。

const obj = { name: "GGBond" };
const arr1 = [obj];
const arr2 = arr1.concat([{ age: 25 }]);

arr1[0].name = "小猪Passion"; // 修改原对象
console.log(arr2[0].name); // "小猪Passion"(影响到了合并后的数组)

Array.prototype.slice()

slice() 方法用于从一个数组中提取部分元素,并返回一个新数组,不会修改原数组。

const obj = { name: "GGBond" };
const arr = [obj, 2, 3];

const newArr = arr.slice();
newArr[0].name = "小猪Passion";

console.log(arr[0].name);  // "小猪Passion"(原数组也受影响)
console.log(newArr[0].name); // "小猪Passion"

扩展运算符 ...

ES6 引入的展开运算符(...)也可以用来实现引用数据类型的浅拷贝。

const obj = { title: "GGBond" };
const arr = [obj, 100, 200];

const newArr = [...arr]; // 使用扩展运算符进行浅拷贝
newArr[0].title = "小猪Passion";

console.log(arr[0].title);  // "小猪Passion"(原数组也受影响)
console.log(newArr[0].title); // "小猪Passion"


Object.create()

Object.create(proto, [propertiesObject]) 方法用于创建一个新对象,并指定该对象的原型(proto)。新创建的对象可以继承 proto 的属性和方法。

const obj = { a: 1, b: 2 };
const shallowCopy = Object.create(obj);

console.log(shallowCopy.a); // 1
console.log(shallowCopy.b); // 2
console.log(shallowCopy.hasOwnProperty('a')); // false(因为它是从原型继承的)

注意:
如果你用 Object.create() 进行浅拷贝,会遇到原型继承的问题:
1.复制的对象属性会变成新对象的原型属性,而不是自身属性
2.不会真正拷贝对象的值,而是创建了一个继承关系

深拷贝(Deep Copy)

深拷贝指的是递归拷贝对象的所有层级,即不仅复制第一层属性,还会创建新的子对象或数组,从而使新对象与原对象完全独立,互不影响。

手写深拷贝

function deepClone(value) {
	if (typeof value !== 'object' || value === null) {
		return value; // 如果是基本数据类型,直接返回
	}
	let objCopy = Array.isArray(value) ? [] : {}; // 区分数组和对象
  for (let key in value) {
    if (value.hasOwnProperty(key)) {
      objCopy[key] = deepClone(value[key]);
    }
  }
  return objCopy;
}

JSON.parse(JSON.stringify())

一种常见的简便方式是使用 JSON.stringify() 将对象序列化为 JSON 字符串,然后使用 JSON.parse()解析为一个新的对象。

const obj = { name: "小猪Passion", age: 18 };

const newObj = JSON.parse(JSON.stringify(obj));
newObj.name = "GGBond";

console.log(obj.name);   // "小猪Passion"(原对象不变)
console.log(newObj.name); // "GGBond"(新对象修改后不影响原对象)

注:

  1. 无法处理非JSON结构的引用数据类型;
  2. 当属性值为undefined时,这些属性会被忽略。

structuredClone()

structuredClone() 是一个现代 JavaScript 内置方法,用于深拷贝对象或数组,能够处理复杂数据类型。

const obj = { name: "小猪Passion", age: 18 };

const newObj = structuredClone(obj);
newObj.name = "GGBond";

console.log(obj.name);   // "小猪Passion"(原对象不变)
console.log(newObj.name); // "GGBond"(新对象修改后不影响原对象)

lodash 库

使用lodashcloneDeep函数进行深拷贝。

// 使用 lodash.cloneDeep() 进行深拷贝
const obj = { name: "小猪Passion", details: { age: 18 } };
const clonedObj = _.cloneDeep(obj);
clonedObj.details.age = 19;

console.log(obj.details.age);   // 18 (原对象不变)
console.log(clonedObj.details.age); // 19 (新对象修改后不影响原对象)

小结

在 JavaScript 中,浅拷贝和深拷贝是处理对象和数组时常用的技术。浅拷贝只复制第一层属性,对于引用类型的属性,只复制引用地址,因此修改拷贝后的对象会影响原对象。而深拷贝则递归地复制所有层级的属性,确保新对象与原对象完全独立,互不影响。

上述时小弟的一点点总结,希望能一次积累进步,若存在错误请指出,谢谢大家!!!


学习参考:
1.深拷贝与浅拷贝:详解 JavaScript 中的数据拷贝机制
2.浅拷贝和深拷贝(较为完整的探索)
3.浅拷贝与深拷贝全面解析及实战


文章荐读:

  1. 🔥🔥🔥Vite6 +TypeScript+Vue3+Tailwind+ESlint+Prettier+Husky搭建企业级前端项目
  2. 📸📸📸前端屏幕录制解决方案探索———WebRTC,html2canvas和rrweb
  3. Vite6 +TypeScript+React18+Tailwind+ESlint+Prettier+Husky搭建企业级前端项目
  4. 🧠🧠🧠由一个BUG引发的对JavaScript运行机制Event Loop的探索