Вопрос: Как правильно клонировать объект JavaScript?


У меня есть объект, x, Я хотел бы скопировать его как объект y, так что изменения yне изменять x, Я понял, что копирование объектов, полученных из встроенных объектов JavaScript, приведет к дополнительным нежелательным свойствам. Это не проблема, так как я копирую один из моих собственных, построенных буквально объектов.

Как правильно клонировать объект JavaScript?


2337


источник


Ответы:


Обновленный ответ

Просто используйте Object.assign () как было предложено Вот


Устаревший ответ

Сделать это для любого объекта в JavaScript не будет простым или простым. Вы столкнетесь с проблемой ошибочного набора атрибутов из прототипа объекта, который должен быть оставлен в прототипе и не скопирован в новый экземпляр. Если, например, вы добавляете cloneметод для Object.prototype, как показывают некоторые ответы, вам нужно будет явно пропустить этот атрибут. Но что, если есть другие дополнительные методы, добавленные к Object.prototype, или другие промежуточные прототипы, о которых вы не знаете? В этом случае вы скопируете атрибуты, которых не должны, поэтому вам необходимо обнаружить непредвиденные, нелокальные атрибуты с помощью hasOwnPropertyметод.

В дополнение к неперечислимым атрибутам вы столкнетесь с более сложной проблемой при попытке копирования объектов, имеющих скрытые свойства. Например, prototypeявляется скрытым свойством функции. Кроме того, прототип объекта ссылается на атрибут __proto__, который также скрыт и не будет скопирован с помощью цикла for / in, итерации по атрибутам исходного объекта. я думаю __proto__может быть специфичным для интерпретатора JavaScript Firefox, и это может быть что-то другое в других браузерах, но вы получаете изображение. Не все перечислимо. Вы можете скопировать скрытый атрибут, если знаете его имя, но я не знаю, как его обнаружить.

Еще одна неприятность в поисках элегантного решения - проблема правильной настройки наследования прототипа. Если прототип исходного объекта Object, а затем просто создавая новый общий объект с {}будет работать, но если прототип источника является потомком Object, то вам не удастся добавить дополнительные элементы из этого прототипа, который вы пропустили, используя hasOwnPropertyфильтр или которые были в прототипе, но не были перечислены в первую очередь. Одним из решений может быть вызов исходного объекта constructorсвойство, чтобы получить исходный объект копирования, а затем скопировать его по атрибутам, но тогда вы все равно не получите неперечислимые атрибуты. Например, Dateобъект сохраняет свои данные как скрытый элемент:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

Строка даты для d1будет на 5 секунд отставать от d2, Способ сделать один Dateто же, что и другое, setTimeметода, но это характерно для Dateкласс. Я не думаю, что это пуленепробиваемое общее решение этой проблемы, хотя я был бы счастлив ошибаться!

Когда мне пришлось выполнить общее глубокое копирование, я оказался в компромиссе, предположив, что мне нужно будет просто скопировать обычную Object, Array, Date, String, Number, или Boolean, Последние 3 типа неизменяемы, поэтому я мог выполнять мелкую копию и не беспокоиться об этом. Я также предположил, что любые элементы, содержащиеся в Objectили Arrayтакже будет одним из 6 простых типов в этом списке. Это может быть выполнено с помощью кода, например:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

Вышеупомянутая функция будет работать адекватно для 6 простых типов, о которых я упоминал, пока данные в объектах и ​​массивах образуют древовидную структуру. То есть, существует не более одной ссылки на одни и те же данные в объекте. Например:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

Он не сможет обрабатывать какой-либо объект JavaScript, но его может быть достаточно для многих целей, если вы не предполагаете, что он будет работать только на все, что вы бросаете на него.


1278



С помощью jQuery вы можете мелкая копия с простираться :

var copiedObject = jQuery.extend({}, originalObject)

последующие изменения в copiedObject не будут влиять на исходный объект, и наоборот.

Или сделать глубокая копия :

var copiedObject = jQuery.extend(true, {}, originalObject)

711



Если вы не используете функции внутри вашего объекта, очень простой один вкладыш может быть следующим:

var cloneOfA = JSON.parse(JSON.stringify(a));

Это работает для всех объектов, содержащих объекты, массивы, строки, булевы и числа.

Смотрите также эта статья о структурированном алгоритме клонирования браузеров который используется при отправке сообщений и от рабочего. Он также содержит функцию глубокого клонирования.


678



В ECMAScript 6 есть Object.assign метод, который копирует значения всех перечислимых собственных свойств из одного объекта в другой. Например:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

Но имейте в виду, что вложенные объекты все еще копируются как ссылка.


478



There are many answers, but none that mentions Object.create from ECMAScript 5, which admittedly does not give you an exact copy, but sets the source as the prototype of the new object.

Thus, this is not an exact answer to the question, but it is a one-line solution and thus elegant. And it works best for 2 cases:

  1. Where such inheritance is useful (duh!)
  2. Where the source object won't be modified, thus making the relation between the 2 objects a non issue.

Example:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

Why do I consider this solution to be superior? It's native, thus no looping, no recursion. However, older browsers will need a polyfill.


113



An elegant way to clone a Javascript object in one line of code

An Object.assign method is part of the ECMAScript 2015 (ES6) standard and does exactly what you need.

var clone = Object.assign({}, obj);

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.

Read more...

The polyfill to support older browsers:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

104



Per MDN:

  • If you want shallow copy, use Object.assign({}, a)
  • For "deep" copy, use JSON.parse(JSON.stringify(a))

There is no need for external libraries but you need to check browser compatibility first.


97



If you're okay with a shallow copy, the underscore.js library has a clone method.

y = _.clone(x);

or you can extend it like

copiedObject = _.extend({},originalObject);

69



There are several issues with most solutions on the internet. So I decided to make a follow-up, which includes, why the accepted answer shouldn't be accepted.

starting situation

I want to deep-copy a Javascript Object with all of its children and their children and so on. But since I'm not kind of a normal developer, my Object has normal properties, circular structures and even nested objects.

So let's create a circular structure and a nested object first.

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

Let's bring everything together in an Object named a.

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

Next, we want to copy a into a variable named b and mutate it.

var b = a;

b.x = 'b';
b.nested.y = 'b';

You know what happened here because if not you wouldn't even land on this great question.

console.log(a, b);

a --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

Now let's find a solution.

JSON

The first attempt I tried was using JSON.

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

Don't waste too much time on it, you'll get TypeError: Converting circular structure to JSON.

Recursive copy (the accepted "answer")

Let's have a look at the accepted answer.

function cloneSO(obj) {
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

Looks good, heh? It's a recursive copy of the object and handles other types as well, like Date, but that wasn't a requirement.

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

Recursion and circular structures doesn't work well together... RangeError: Maximum call stack size exceeded

native solution

After arguing with my co-worker, my boss asked us what happened, and he found a simple solution after some googling. It's called Object.create.

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

This solution was added to Javascript some time ago and even handles circular structure.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

... and you see, it didn't work with the nested structure inside.

polyfill for the native solution

There's a polyfill for Object.create in the older browser just like the IE 8. It's something like recommended by Mozilla, and of course, it's not perfect and results in the same problem as the native solution.

function F() {};
function clonePF(o) {
    F.prototype = o;
    return new F();
}

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

I've put F outside the scope so we can have a look at what instanceof tells us.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> F {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

Same problem as the native solution, but a little bit worse output.

the better (but not perfect) solution

When digging around, I found a similar question (In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being "this"?) to this one, but with a way better solution.

function cloneDR(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) {
        return o; // primitive value
    }

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = cloneDR(o[i]);
        }
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    }
    if (set) {
        o[gdcc] = cache; // reset
    } else {
        delete o[gdcc]; // unset again
    }
    return result;
}

var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

And let's have a look at the output...

console.log(a, b);

a --> Object {
    x: "a",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "a"
    }
}

b --> Object {
    x: "b",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

The requirements are matched, but there are still some smaller issues, including changing the instance of nested and circ to Object.

The structure of trees that share a leaf won't be copied, they will become two independent leaves:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

conclusion

The last solution using recursion and a cache, may not be the best, but it's a real deep-copy of the object. It handles simple properties, circular structures and nested object, but it will mess up the instance of them while cloning.

http://jsfiddle.net/einfallstoll/N4mr2/


62



One particularly inelegant solution is to use JSON encoding to make deep copies of objects that do not have member methods. The methodology is to JSON encode your target object, then by decoding it, you get the copy you are looking for. You can decode as many times as you want to make as many copies as you need.

Of course, functions do not belong in JSON, so this only works for objects without member methods.

This methodology was perfect for my use case, since I'm storing JSON blobs in a key-value store, and when they are exposed as objects in a JavaScript API, each object actually contains a copy of the original state of the object so we can calculate the delta after the caller has mutated the exposed object.

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

34



For those using AngularJS, there is also direct method for cloning or extending of the objects in this library.

var destination = angular.copy(source);

or

angular.copy(source, destination);

More in angular.copy documentation...


23



You can simply use a spread property to copy an object without references. But be careful (see comments), the 'copy' is just on the lowest object/array level. Nested properties are still references!


Complete clone:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

Clone with references on second level:

const y = {a: {b: 'value3'}}
const y2 = {...y}

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript actually does not support deep clones natively. Use an utility function. For example Ramda:

http://ramdajs.com/docs/#clone


21