js对象的5种拷贝方式以及副作用,你知道几种?

148 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

今天聊聊前端中对象的几种常见的拷贝方式,以及限制条件。

1.Object.assign(只会拷贝源对象可枚举的和自身的属性到目标对象)

这种方式是比较常见的方式,大家应该也都用过。我这截了一张官方文档对Object.assign方法的说明。

在使用的时候,需要注意(Object.assign() 不会在 source 对象值为 [null]时抛出错误)

image.png

并且,关于深浅拷贝,文档也有具体的说明

image.png

所以说,这是一个典型的浅拷贝方法。

2. es6的对象解析

对象的解析,其实就是一种key,value的重新赋值,很明显也是一种浅拷贝

let a = {
    age: 12,
    like: ['apple', 'banana']
}

let b = { ... a }
a.like[0] = 'orange'

console.log(b) // { age: 12, like: ['orange', 'banana'] }

3.JSON.stringify/structuredClone

JSON也是我们经常用的一种方法,但是它也是有限制条件的。

在文档上有这样一句话,“One way to make a deep copy of a JavaScript object, if it can be [serialized]”

如果数据是可序列化的,那么可以用JSON来进行深拷贝。

image.png

然而,一旦数据不能序列化,那么使用JSON进行拷贝就会有一定的问题,比如像函数,dom元素,symbols之类的数据。

关于这一点,文档中也有具体的说明。

image.png

在文档的最后,还还介绍了一种深拷贝方式,structuredClone方法,相比较于JSON,两者之间的用法很相似,唯一的区别是structuredClone可以转移源。

**4.MessageClone **

这个方法其实并不算是用来拷贝的,而是用于iframe通信的,但是这个方法也是可以实现数据的深拷贝的,只不过比较冷门,这里也做一下介绍。

const MessageClone = (obj) => {
  return new Promise((resolve) => {
    const { port1, port2 } = new MessageChannel();
    port2.onmessage = (mes) => resolve(mes.data);
    port1.postMessage(obj);
  });
};

let obj = {
    a: {
      b: 1,
      arr: [1, 2, 3, 4],
    },
  }, newObj = null;

MessageClone(obj).then((obj) => {
  newObj = obj;
  console.log(newObj);
});

5.递归

介绍了这么多,目前还没有一个特别完善的方法。

虽然之前的方法都可以实现拷贝,但多少都有一点问题,比如我们要拷贝原型链方法,函数之类的数据。

所以想要彻底的进行深拷贝,还是得用递归方法。

目前,像lodash之类的框架,里面的深拷贝方法也是用的递归方法。

这里实现一个简单的版本,只考虑相对简单的情况, 像lodash里面是加了很多判断,以及类型兼容的,有兴趣可以看一下lodash中的深拷方法。

注意: (为了能拷贝原型链的方式,使用对象的构造函数来创建变量。)

function deepClone(data) {
  let obj = null;
  if (Object.prototype.toString.call(data) === "[object Array]") {
    obj = new data.constructor();
    for (let i = 0; i < data.length; i++) {
      obj.push(deepClone(data[i]));
    }
  } else if (Object.prototype.toString.call(data) === "[object Object]") {
    obj = new data.constructor();
    for (let key in data) {
      if (data.hasOwnProperty(key)) {
        obj[key] = deepClone(data[key]);
      }
    }
  } else {
    return data;
  }
  return obj;
}

目前来说,递归方法是唯一一个没有副作用的方法,唯一的缺点就是实现比较麻烦。