How to create a copy of an object in Javascript?

Asked

Viewed 14,487 times

38

I have an object called cachorro and would like to create and store a copy of that object.

As objects, vectors, functions and regular expressions are considered objects I cannot make a copy just by assigning this object to another variable (eg.: var cachorro2 = cachorro;), because they are objects only one reference is passed to the other variable.

I wonder if there is any way to make a copy of an object, whatever its type (objects, vectors, functions or regular expressions)?

8 answers

36


Copy an object often not a simple task; there are different techniques with their respective pros and cons.

It is especially difficult to offer a complete Javascript solution. Some have performance problems. Others do not work for specific types, such as Date. Endure circular reference is laborious. And almost all of them bump into the problem of prototype (very well explained here).

Clone or copy?

A common confusion is to differentiate the terms clone and copy. In some scenarios the two terms are used interchangeably, referring both to a copy of an object. In others, clone takes a proper meaning, means to copy only the structure of the object (in this case, the prototype).

There is only the caveat; I will not deepen the dicussion and use the most correct term, copy.

Shallow copy vs deep copy

It is important to differentiate the two most common types of copies: Shallow and deep.

A copy Shallow (superficial) makes a copy of the original object, but the properties of the original object still share reference with those of the copy object. Understand how the object "father" is copied but divides your "children".

Already the copy deep (deep) also makes a copy of the properties; the copy of the parent object has its own children (which are also copies).

The following examples are from deep copy, which is what is normally desired.

JSON.stringify & JSON.parse

var copia = JSON.parse(JSON.stringify(original));

The biggest problem of this method is that the functions of the original object would not be present in the copied object.

This is the method that everyone ends up using when the copy doesn’t need to take into account the various other complexities left behind.

Beware of circular references as this method does not support them ("Typeerror: Cyclic Object value").

val & toSource

var copia = eval(original.toSource());

This is one of the worst solutions, as always, Eval is evil, beyond the method toSource not be standard. It’s just as curiosity; do not recommend.

Method of Brian Huisman

function copiarObj(original) {
  var copia = (original instanceof Array) ? [] : {}; // verificando se é um array ou um objeto 'comum' e instanciando a cópia
  for (i in original) { // iterando cada propriedade do objeto original
    if (original[i] && typeof original[i] == 'object') copia[i] = copiarObj(original[i]); // se for um objeto realiza cópia desse objeto (note a recursividade aqui)
    else copia[i] = original[i]; // se não simplesmente copia o valor
  }
  return copia; // retorna a cópia
};

I shortened it lightly the original method to facilitate teaching.

There are some small problems, such as not working for objects of the type Date, but overall it uses a simple loop for. in and it ends up being very simple. The secret so that it is not a copy Shallow is the recursion made.

Be careful again with circular references, as it is not treated in the loop (generating an infinite loop).

jQuery.extend

var copia = $.extend(true, {}, original);

I decided to dedicate a part exclusively to jQuery, which, despite being a library and will not be available in all scenarios, is widely used.

The first parameter (true) is for jQuery to make a deep copy; the second ({ }) is the target object, in our case a new object (which is returned by the function).

More complex solutions

As I said, copying an object in Javascript correctly is not an easy task. I have tried to offer only more common and simple examples, since the question does not appear to be for complex cases.

Here are some solutions links that try to be more complete: [1], [2], [3], [4].

Use your framework

If you use a large framework, just like jQuery, it may offer you such a function, such as Underscore.js or the Mootools.

If you are using Node.js there is the module clone (npm, github).

  • 2

    JSON.stringify throws an exception if the object has circular reference.

  • Good remark, I’ll add to the answer.

  • 1

    Brian Huisman’s method causes an infinite loop in circular reference, test: http://jsfiddle.net/gB55W/

  • Depending on the situation Object.assign can solve the problem, but it copies only enumerable and proper properties of a source object to a destination object. This can make it inappropriate to combine new properties with a prototype if the source objects contain getters. To copy property definitions, including their enumerability, to prototypes Object.getOwnPropertyDescriptor() and Object.defineProperty() should be used instead. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

10

Copying an object (with all its attributes and methods/functions) is not a single response task.

In the case of more complex objects I recommend the use of a library such as the Mootools which has a specific function/method for this: var clone = Object.clone(obj);

Here’s an example to copy a simple object with javascript:

Object:

var obj = {
    ano: 2013,
    param2: ['a', 'b', 'c', 'd', 'c']
};

A function that traverses its properties and makes a copy for a temporary object

function copiarObjecto(obj) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
    var temp = obj.constructor();
    for (var key in obj) {
        temp[key] = copiarObjecto(obj[key]);
    }
    return temp;
}

Set function response when passing an object as parameter

var objCopia = copiarObjecto(obj);

Example

  • 4

    Good answer, simple and concise, using pure Javascript! One only needs to be careful with objects that contain cycles (i.e. references to themselves). See an alternative solution for such cases.

  • var temp = obj.constructor(); can cause problems if the constructor depends on passing parameters (and there was no missing one new?). Maybe it’s safer something like function F(){}; F.prototype = Object.create(obj); var temp = new F(); to secure the inheritance - if the goal was that.

7

The most "secure" (i.e. less error prone) way to clone an object is by using an existing library/framework that supports this function, since cloning in theory is simple (see @Sergio’s reply, for example) but in practice there are details and edge cases (unusual cases) that may bring an incorrect result and/or cause the operation to fail.

An example is an object with cycles (i.e. containing a reference to itself):

var obj = { x:42 };
obj.y = [obj];

Most popular libraries do not support cloning of these objects:

// Mootools
console.log(Object.clone(obj)); // Uncaught RangeError: Maximum call stack size exceeded

// jQuery
console.log($.extend(true, {}, obj)); // Uncaught RangeError: Maximum call stack size exceeded 

// Underscore.js (Não dá suporte a cópias profundas)
console.log(_.clone(obj).y[0] === obj); // true

In this case, a solution would be to use the utility cycle.js from the JSON-js library:

var clone = JSON.decycle(obj); // Remove os ciclos
clone = $.extend(true, {}, clone); // Faz a clonagem de fato (pelo método de sua escolha)
clone = JSON.retrocycle(clone); // Refaz os ciclos

console.log(clone === clone.y[0]); // true
  • Interesting that in my reproduction of extend seems to work well and does not give the problem of Maximum call stack. Example: http://jsfiddle.net/2Q4Xd/

  • @Brunolm Experimenta console.log(copia === copia.y[0]); // false and console.log(original === copia.y[0]); // true. I haven’t gone through his code in depth to find out where the problem is, but the fact is that he’s not actually making a deep copy (since copia contains a reference to original).

4

I believe you can do so, if you are using jQuery:

var cachorro2 = $.extend(true, {}, cachorro);

or else with Underscore:

var cachorro2 = _.clone(cachorro);

Amendments subsequent to cachorro2 no longer alter the object cachorro and vice versa.


Unfortunately these solutions do not work with Regex, Date, etc... Therefore, the most elegant method of doing this is creating a function like this extend:

function extend(from, to) {
  if (from == null || typeof from != "object") return from;
  if (from.constructor != Object && from.constructor != Array) return from;
  if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
      from.constructor == String || from.constructor == Number || from.constructor == Boolean)
      return new from.constructor(from);

  to = to || new from.constructor();

  for (var name in from) {
    to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
  }

  return to;
}

And you can use it like this:

var cachorro2 = extend(cachorro);

See the example.

  • I don’t know underscore and lodash, but if I’m not mistaken I read that the underscore clone is not deep copy, already in the lodash has it. I would confirm?

  • @Brunolm Yes, unlike the Underscore, Lodash has the cloneDeep besides the clone. http://lodash.com/docs#cloneDeep

3

I believe the most modern way (that works in all modern browsers) would be to use Object.assign() that can make everything simpler and more efficient, rather than using loops or JSON.parse() much less jQuery or any external lib, a very simple example:

var original = {
    "foo": "bar"
};

var copia = Object.assign({}, original);
copia.foo = "Olá, Stack Overflow";

//O original não é afetado, pois não é mais uma referencia
console.log("original:", original);

//Exibe o copia
console.log("copia:", copia);

If you try to set directly, it occurs of referencing, which will affect the original:

var original = {
    "foo": "bar"
};

var copia = original;
copia.foo = "Olá, Stack Overflow";

//O original não é afetado, pois não é mais uma referencia
console.log("original:", original);

//Exibe o copia
console.log("copia:", copia);


Compatibility

According to MDN, supported browsers are:

  • Chrome 45+
  • Edge
  • Firefox 34
  • Safari 9

Yet there is a "Polyfill" for older browsers suggested by the MDN itself that allows you to use in older browsers:

if (typeof Object.assign != 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
      'use strict';
      if (target == null) { // TypeError if undefined or null
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) { // Skip over if undefined or null
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

2

Another way to create a copy would be by making use of Object.create().

Remembering that it is a new ES5 method, see Compatibility.

Example:

a = {};
b = Object.create(a);
b.nome = 'Gabriel';
a.nome = 'Rodrigues';

console.log("a:", a);
console.log("b:", b);

  • Gabriel, this is not copying the object. Just creating a new object whose [[Prototype]] is the object you want to copy. How references will be kept, the copy, actually, nay occurred.

2

An isolated copy of the method extend jQuery would be like this:

First a function is needed to see if the object should be cloned or not

// para evitar loops infinitos o jQuery clona apenas objetos simples
// objetos complexos ele mantem a referencia

// este método verifica se um objeto é simples ou se é uma instancia
function isPlainObject(obj)
{
    if (typeof (obj) !== "object" || obj.nodeType || (obj instanceof Window))
        return false;

    try
    {
        if (obj.constructor &&
            !({}).hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf"))
        {
            return false;
        }
    }
    catch (e)
    {
        return false;
    }

    return true;
}

And the function to clone:

function extend(deep, target, source)
{
    var copy, original, clone;
    for (var attr in source)
    {
        copy = target[attr];
        original = source[attr];

        if (target === original)
            continue;

        // se for uma cópia profunda e o objeto for simples
        if (deep && original && isPlainObject(original))
        {
            // obtem um objeto simples vazio para ser estendido

            if (original instanceof Array)
            {
                clone = copy && (copy instanceof Array) ? copy : [];
            }
            else
            {
                clone = copy ? copy : {};
            }

            // recursivamente estende o objeto interno 
            target[attr] = extend(deep, clone, original);
        }
        else if (original !== undefined)
        {
            // copia o valor para o objeto de destino
            // caso o objeto seja uma referencia o valor não é clonado
            target[attr] = original;
        }
    }

    // retorna o objeto estendido
    return target;
}

For testing

function Pessoa(params)
{
    this.id = params['id'];
    this.nome = params['nome'];
    this.pai = null;
    this.toString = function () { return this.nome };
}

var luke = new Pessoa({ id: 1, nome: 'Luke' });
var vader = new Pessoa({ id: 2, nome: 'Darth' });
luke.pai = vader;
vader.pai = luke; // haha

var copia = extend(true, {}, luke);
luke.nome = "Luke Skywalker";

console.log(luke.nome);  // Luke Skywalker
console.log(copia.nome); // Luke

I created a example to demonstrate jsFiddle operation.

Note: I made some modifications and do not guarantee compatibility with old browsers.

-1

An alternative to creating an Object whose prototype inherits another Object is the function developed by Douglas Crockford:

if (typeof Object.create !== 'function') {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}
newObject = Object.create(oldObject);

The reference is found here.

  • This is not copying the object. Just creating a new object whose [[Prototype]] is the object you want to copy. How references will be kept, the copy, actually, nay occurred. Apart from that the polyfill is incorrect, since Object.create accept two arguments.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.