浅拷贝与深拷贝

浅拷贝

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

实现方式

前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);
}