Orion's Studio.

浅拷贝与深拷贝

2024/03/23

浅拷贝

创建一个对象的复制,但是其中的元素都是原始对象的引用,所以只解决了第一层的拷贝。如果原始对象的元素变化,在浅拷贝中也会体现这些变化。

实现方式

前2种是同时支持对象和数组的拷贝:

实现方式1 (Object 方法)

1
2
const b = Object.assign({}, a);
const c = Object.create(a);

实现方式2 (展开运算符)

1
2
const b = { ...a };
const c = [...a];

以下是只针对数组的拷贝:

实现方式3 (Array.prototype.concat())

1
const b = [].concat(a);

实现方式4 (Array.prototype.slice())

1
const b = a.slice();

实现方式5 (Array.from())

1
const b = Array.from(a);

深拷贝

创建一个对象的全新复制,会递归地复制对象的所有元素和子元素。如果原始对象的元素变化,深拷贝创建的对象不受影响。

实现方式

实现方式1 (JSON 序列化):

1
const b = JSON.parse(JSON.stringify(a));

上述方法的局限性:

  • 会忽略值为 undefined、symbol 或函数的属性
  • 不支持循环引用,会直接报错

    实现方式2 (2022年新特性,core-js 已支持该 polyfill):

    1
    2
    3
    4
    5
    6
    const b = window.structuredClone(a);

    const arr = new Uint8Array();
    const c = window.structuredClone(arr, {
    transfer: [arr.buffer] // 使可转移对象转移到新的对象
    })
  • 支持循环引用
  • 还是不支持序列化,含有值为 undefined、symbol 或函数的属性,会直接报错

实现方式3 (MessageChannel):

1
2
3
4
5
6
7
const mySturcturedClone = (obj) => new Promise((resolve) => {
const { port1, port2} = new MessageChannel();
port2.onmessage = (ev) => resolve(ev.data);
port1.postMessage(obj);
});

const b = await mySturcturedClone(a);
  • 支持值为 undefined 的属性
  • 支持循环引用
  • 还是不支持 symbol 或函数的属性,会直接报错

实现方式4 (递归遍历):

```javascript
const myStructuredClone = (origin) => {
const map = new WeakMap(); // 因为对象的 key 可能是对象,所以使用 WeakMap
const deepClone = (obj) => {
if (typeof obj !== ‘object’ || obj === null) return obj; // 基本类型直接返回
if (map.has(obj)) return map.get(obj); // 防止循环引用
const clone = Array.isArray(obj) ? [] : {}; // 判断是数组还是对象
map.set(obj, clone);
for (const key in obj) { // 同时可以遍历数组和对象,数组的 key 为索引
// Object 是一个构造函数,所以要取 Object.prototype 对象上的方法
if (Object.prototype.hasOwnProperty.call(obj, key)) { // 判断是否是自身属性,而不是原型链上的属性
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
return deepClone(origin);
}

CATALOG
  1. 1. 浅拷贝
    1. 1.1. 实现方式
      1. 1.1.1. 实现方式1 (Object 方法)
      2. 1.1.2. 实现方式2 (展开运算符)
      3. 1.1.3. 实现方式3 (Array.prototype.concat())
      4. 1.1.4. 实现方式4 (Array.prototype.slice())
      5. 1.1.5. 实现方式5 (Array.from())
  2. 2. 深拷贝
    1. 2.1. 实现方式
      1. 2.1.1. 实现方式1 (JSON 序列化):
      2. 2.1.2. 实现方式2 (2022年新特性,core-js 已支持该 polyfill):
      3. 2.1.3. 实现方式3 (MessageChannel):
      4. 2.1.4. 实现方式4 (递归遍历):