JavaScript 对象复制
对象复制,深浅拷贝,引用类型

JavaScript 对象复制这件事之前直接 copy 的 stackoverflow 上的方法: JSON.parse(JSON.stringify(food))。最近看了几篇文章, 总结抄袭了一下。

首先,JavaScript 的对象是引用类型,所以我们没法直接用=来进行 copy。目前常用的有这么三种:


const vehicle = { bike: 🚲, ufo: 🛸 };

// spread操作符
const vehicle1 = { ...vehicle };

// Object.prototyp.assign
const vehicle2 = Object.assign({}, vehicle);

// "JSON"
const vehicle3 = JSON.parse(JSON.stringify(vehicle));

console.log(vehicle1, vehicle2, vehicle3);

对象是引用类型

你可能首先想问,为什么不能直接用=?我们来看下如果我们这么做,会发生什么:

const obj = { one: 1, two: 2 };
const obj2 = obj;

console.log(obj, obj2);
// {one: 1, two: 2}
// {one: 1, two: 2}

目前这两个对象的输出看起来是一样的。我们看下如果编辑第二个对象会发生什么?

const obj2.three = 3;

console.log(obj2);
// {one: 1, two: 2, three: 3}; <-- ✔

console.log(obj);
// {one: 1, two: 2, three: 3}; <-- 😱

啥情况?我们明明只改了obj2为什么obj也被改了?这是因为 Objects 是引用类型。所以当你使用=,它会拷贝指向这块内存地址的指针。引用类型不持有值本身,他们只是一个指向内存空间中这个值的指针。

1. 使用展开运算符

使用展开运算符会克隆你的对象。不过这只是浅拷贝。

const food = { beef: "🥩", bacon: "🥓" };

const cloneFood = { ...food };

console.log(cloneFood);
// { beef: '🥩', bacon: '🥓' }

2. 使用 Object.assign

或者用Object.assign方法,这个也是用来生成一个对象的浅拷贝对象。

const food = { beef: "🥩", bacon: "🥓" };

const cloneFood = Object.assign({}, food);

console.log(cloneFood);
// { beef: '🥩', bacon: '🥓' }

需要注意的是{}作为第一个参数传进去, 这样做会确保你不会更改原有的对象。

3. 使用 JSON

这个最终的办法会让你获得一个深拷贝对象。正像我之前提到的,这样做只是一个“便捷”并且不那么“完美”的办法去深拷贝一个对象。如果要更完美的方案,我推荐lodash

const food = { beef: "🥩", bacon: "🥓" };

const cloneFood = JSON.parse(JSON.stringify(food));

console.log(cloneFood);
// { beef: '🥩', bacon: '🥓' }

Lodash DeepClone vs JSON

deepClone 和 JSON.stringify/parse 的区别:

  • JSON.stringify/parse只作用于 Number 和 String 以及没有 function 和 Symbol 属性的 Object。

  • deepClone可以作用于所有的类型, function 和 Symbol 被作为引用拷贝过去。

这有一个例子

const lodashClonedeep = require("lodash.clonedeep");

const arrOfFunction = [
  () => 2,
  {
    test: () => 3
  },
  Symbol("4")
];

// deepClone copy by refence function and Symbol
console.log(lodashClonedeep(arrOfFunction));
// JSON replace function with null and function in object with undefined
console.log(JSON.parse(JSON.stringify(arrOfFunction)));

// function and symbol are copied by reference in deepClone
console.log(
  lodashClonedeep(arrOfFunction)[0] === lodashClonedeep(arrOfFunction)[0]
);
console.log(
  lodashClonedeep(arrOfFunction)[2] === lodashClonedeep(arrOfFunction)[2]
);

深拷贝 vs 浅拷贝

当使用...copy 对象的时候, 只是创建一个浅拷贝。如果一个数组是嵌套的或者多维的, 这个是搞不定的。下边是例子:

const nestedObject = {
  country: 'US',
  {
    city: 'New York'
  }
}

浅拷贝

我们使用展开运算符来克隆一个对象试一下:

const shallowClone = { ...nestedObject };

// Changed our cloned object
clonedNestedObject.country = "CA";
clonedNestedObject.country.city = "Toronto";

上边代码里我们改变了对象的 city 属性,看一下输出:

console.log(shallowClone);
// {country: 'CA', {city: 'Toronto'}} <-- ✅

console.log(nestedObject);
// {country: 'US', {city: 'Toronto'}} <-- 😱

这个浅拷贝的例子表明了, 对象的第一级属性是被拷贝了,但是更深一层的都只是引用。

深拷贝

现在看一个用 JSON 来实现深拷贝的例子:

const deepClone = JSON.parse(JSON.stringify(nestedObject));

console.log(deepClone);
// {country: 'CA', {city: 'Toronto'}} <-- ✅

console.log(nestedObject);
// {country: 'US', {city: 'New York'}} <-- ✅

就像例子里显示的那样,深拷贝可以真正的拷贝嵌套对象。

性能

暂时只说结果,Object.assign性能要远好于JSON


Last modified on 2020-03-01