How to Clone Objects in JavaScript


Because objects in JavaScript are references values, you can’t simply just copy using the =. But no worries, here are 4 ways for you to clone an object 👍

  • Using Spread(...) operator
  • Using Object.assign()
  • Using JSON.stringify() and JSON.parse()
  • Using recursive function

Objects are Reference Types

Your first question might be, why can’t I use =. Let’s see what happens if we do that:

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

const obj2 = obj;

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

So far, both object seems to output the same thing. So no problem, right. But let’s see what happens if we edit our second object:

const obj2.three = 3;

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

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

WTH?! I changed obj2 but why was obj also affected. That’s because Objects are reference types. So when you use =, it copied the pointer to the memory space it occupies. Reference types don’t hold values, they are a pointer to the value in memory.

Shallow Clone vs Deep Clone

Object cloning is something that we may often come across while coding. As we know, Objects and Arrays in JavaScript are reference type, if we normally copies an object or array then it will do a shallow copy, not a deep copy. If nested objects are present inside an object then deep copy is must.

对象克隆是我们在编码时经常会遇到的问题.众所周知,JavaScript 中的对象和数组是引用类型,我们通常复制对象或数组,对它进行浅复制,而不是深复制.如果对象内部存在嵌套对象,则必须进行深复制.

When working with functional programming a good rule of thumb is to always create new objects instead of changing old ones. In doing so we can be sure that our meddling with the object’s structure won’t affect some seemingly unrelated part of the application, which in turn makes the entire code more predictable.

使用函数式编程时,一个好的法则是始终创建新对象,而不是更改旧对象.这样一来,我们可以确保干预对象的结构不会影响应用程序中某些看似无关的部分,从而使整个代码更具可预测性.

How exactly can we be sure that the changes we make to an object do not affect the code elsewhere? Removing the unwanted references altogether seems like a good idea. To get rid of a reference we need to copy all of the object’s properties to a new object.

我们如何确切地确定对对象所做的更改不会影响其他地方的代码?完全删除不需要的引用似乎是个好主意.为了摆脱参考,我们需要将对象的所有属性复制到新对象.

Shallow Cloning

To shallow copy, an object means to simply create a new object with the exact same set of properties. We call the copy shallow because the properties in the target object can still hold references to those in the source object.

对于浅复制,对象意味着简单地创建具有完全相同的一组属性的新对象.我们称为浅复制,因为目标对象中的属性仍然可以保留对源对象中那些属性的引用.

Using Spread(...) operator

When I used spread ... to copy an object, I’m only creating a shallow copy. If the array is nested or multi-dimensional, it won’t work. Here’s our example we will be using:

Using spread will clone the object. Note this will be a shallow copy.

const obj = {
  brand: 'Apple',
  storage: {
    ram: '8GB',
    memory: '128GB',
  },
};

Let’s clone our object using spread:

const shallowClone = { ...obj };

// Changed our cloned object
shallowClone.brand = 'Huawei';
shallowClone.storage.memory = '512GB';

So we changed our cloned object. Let’s see the output.

console.log(obj);
// { brand: "Apple", storage: Object { ram: "8GB", memory: "512GB" } } <-- 😱

console.log(shallowClone);
// { brand: "Huawei", storage: Object { ram: "8GB", memory: "512GB" } }

A shallow copy means the first level is copied, deeper levels are referenced.

If nested objects are not present, then spread(…) operator can work fine for cloning objects.

如果不存在嵌套对象,展开语法可以很好地用于克隆对象.

Using Object.assign()

Alternatively, Object.assign is in the official released and will also create a shallow copy of the object.

Note the empty {} as the first argument, this will ensure you don’t mutate the original object 👍

const obj = {
  brand: 'Apple',
  storage: {
    ram: '8GB',
    memory: '128GB',
  },
};

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

shallowClone.brand = 'Huawei';
shallowClone.storage.memory = '512GB';

console.log(obj);
// { brand: "Apple", storage: Object { ram: "8GB", memory: "512GB" } } <-- 😱

console.log(shallowClone);
// { brand: "Huawei", storage: Object { ram: "8GB", memory: "512GB" } }

If nested objects are not present, then Object.assign() can work fine for cloning objects.

如果不存在嵌套对象,Object.assign()可以很好地用于克隆对象.

Let’s see how we can achieve deep cloning for multi level objects also in JavaScript.

Deep Cloning of multi level Objects

When we make a deep copy we create a completely new object which holds no references to the original.

进行深复制时,我们将创建一个全新的对象,该对象不包含对原始对象的引用.

Using JSON.stringify() and JSON.parse()

This final way will give you a deep copy. Now I will mention, this is a quick and dirty way of deep cloning an object. For a more robust solution, I would recommend using something like lodash

Now, let’s take a different approach. Our goal is to create a new object without any reference to the previous one, right? Why don’t we use the JSON object then? First, we stringify the object, then parse the resulting string. What we get is a new object totally unaware of its origin.

现在,让我们采用另一种方法.我们的目标是创建一个新对象,而不引用任何上一个对象,对吗?那为什么我们不使用 JSON 对象呢?首先,我们对对象进行字符串化,然后解析结果字符串.我们得到的是一个完全不知道其起源的新物体.

Note: In the solution the methods of the object aren’t retained. JSON format does not support functions, therefore they are just removed altogether.

注意:在解决方案中,没有保留了对象的方法. JSON 格式不支持功能,因此将它们完全删除.

First convert your original object into string and then parse it back to JSON object from it and voila! Your new deeply cloned object is ready.

首先将原始对象转换为字符串,然后将其解析回 JSON 对象,瞧!您的新的深度克隆对象已准备就绪.

const obj = {
  brand: 'Apple',
  storage: {
    ram: '8GB',
    memory: '128GB',
  },
};

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

deepClone.brand = 'Huawei';
deepClone.storage.memory = '512GB';

console.log(obj);
// { brand: "Apple", storage: Object { ram: "8GB", memory: "128GB" } }

console.log(deepClone);
// { brand: "Huawei", storage: Object { ram: "8GB", memory: "512GB" } }

This technique (JSON.parse(JSON.stringify(obj))) doesn’t work if your object property contains function as value. Because when you JSON.stringify the object, the property containing function as value gets removed from the object. So go for recursive function approach in such case.

⚠️ 如果你的对象包含 function, undefined or NaN 值的话,JSON.parse(JSON.stringify(obj)) 就不会有效.因为当你 JSON.stringify 对象的时候,包含function, undefined or NaN值的属性会从对象中移除. 因此,在这种情况下,请使用递归函数方法.

Using recursive function

Our first implementation works recursively. We write a deep function, which checks the type of the argument sent to it and either calls an appropriate function for the argument being an array or an object or simply returns the value of the argument (if it is neither an array nor an object).

我们的第一个实现是递归工作的.我们编写了一个深函数,该函数检查发送给它的参数的类型,并为作为数组或对象的参数调用适当的函数,或者简单地返回参数的值(如果它既不是数组又不是对象) .

To deep clone an object, you need to iterate through each property and check if the current property contains an object. If yes, then do a recursive call to the same function by passing the current property value (i.e. the nested object). Look at the below example.

要深度克隆对象,您需要遍历每个属性,并检查当前属性是否包含对象.如果是,则通过传递当前属性值(即嵌套对象)对同一函数进行递归调用.看下面的例子.

const phone = {
  brand: 'Apple',
  storage: {
    ram: '8GB',
    memory: '128GB',
  },
};

const makeDeepClone = (phone) => {
  let newObject = {};
  Object.keys(phone).map((key) => {
    if (typeof phone[key] === 'object') {
      newObject[key] = makeDeepClone(phone[key]);
    } else {
      newObject[key] = phone[key];
    }
  });
  return newObject;
};

const deepClone = makeDeepClone(phone);

deepClone.brand = 'Huawei';

deepClone.storage.memory = '512GB';

console.log(phone);
// {brand: 'Apple', storage:{ram: '8GB', memory: '128GB'}}

console.log(deepClone);
// {brand: 'Huawei', storage:{ram: '8GB', memory: '512GB'}}

The makeDeepClone function takes all of the keys of an object and iterates over them, recursively calling the makeDeepClone function for each value.

makeDeepClone 函数获取对象的所有键并对其进行迭代,为每个值递归调用 makeDeepClone 函数.

Deep Clone using External Libraries

  • @lesjeuxdebebel : Personally I use jQuerywith $.extend(); function
  • @edlinkiii : underscore.js ~~ _.clone()
  • @Percy_Burton : The only way I’ve known to do this is with the Lodash library, cloneDeep method.

cloneDeepWith This method is like _.cloneWith except that it recursively clones value.

Lodash DeepClone vs JSON

Here’s a comment from the community. Yes, it was for my previous post, How to Deep Clone an Array . But the idea still applies to objects.

Alfredo Salzillo : I’d like you to note that there are some differences between deepClone and JSON.stringify/parse.

  • JSON.stringify/parse only work with Number and String and Object literal without function or Symbol properties.
  • deepClone work with all types, function and Symbol are copied by reference.

Here’s an example:

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]);

@OlegVaraksin : The JSON method has troubles with circular dependencies. Furthermore, the order of properties in the cloned object may be different.

More Ways using JavaScript

  • @hariharan_d3v : Object.fromEntries(Object.entries(food)) [shallow] clones the object.

Resources


如何写出一个惊艳面试官的深拷贝

3 Ways to Clone Objects in JavaScript

How to Deep Clone Objects in JavaScript

Difference between Shallow copy and Deep copy in JavaScript

实现一个函数 clone(), 可以对 JavaScript 中的 5 种主要的数据类型 (包括 Number、String、Object、Array、Boolean) 进行值复制.

解析

这道题考察了以下知识点:

  • 使用 typeof 判断值得类型;
  • 使用 toString 区分数组和对象;
  • 递归函数的使用;
function clone(obj) {
  // 判断是对象, 就进行循环复制
  if (typeof obj === 'object' && typeof obj !== 'null') {
    // 区分是数组还是对象, 创建空的数组或对象
    var o = Object.prototype.toString.call(obj).slice(8, -1) === 'Array' ? [] : {};
    for (var k in obj) {
      // 如果属性对应的值为对象, 则递归复制
      if (typeof obj[k] === 'object' && typeof obj[k] !== 'null') {
        o[k] = clone(obj[k]);
      } else {
        o[k] = obj[k];
      }
    }
  } else {
    // 不是对象, 直接把值返回
    return obj;
  }
  return o;
}

String.prototype.slice() - JavaScript | MDN: slice() 方法提取某个字符串的一部分, 并返回一个新的字符串, 且不会改动原字符串.


文章作者: 庄引
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 庄引 !
  目录