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





15 Answers

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

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

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

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

объекты глубокое клонирование

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

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




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

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

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




Существует много ответов, но никто из них не упоминает Object.create из ECMAScript 5, который, по общему признанию, не дает вам точной копии, но устанавливает источник как прототип нового объекта.

Таким образом, это не точный ответ на вопрос, но он является однострочным решением и, следовательно, элегантным. И он работает лучше всего для 2-х случаев:

  1. Где такое наследование полезно (duh!)
  2. Если исходный объект не будет изменен, что делает связь между двумя объектами без проблем.

Пример:

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

Почему я считаю это решение превосходным? Это родной, поэтому нет циклов, нет рекурсии. Однако для старых браузеров потребуется полипол.




Если вы в порядке с мелкой копией, библиотека underscore.js имеет метод clone .

y = _.clone(x);

или вы можете расширить его, как

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



Одним из особенно неэлегантных решений является использование кодировки JSON для создания глубоких копий объектов, которые не имеют методов-членов. Методология заключается в том, что JSON кодирует ваш целевой объект, затем, декодируя его, вы получаете копию, которую ищете. Вы можете декодировать столько раз, сколько хотите сделать столько копий, сколько вам нужно.

Конечно, функции не принадлежат JSON, поэтому это работает только для объектов без методов-членов.

Эта методология была идеальна для моего варианта использования, поскольку я храню капли JSON в хранилище ключей и когда они отображаются как объекты в JavaScript API, каждый объект фактически содержит копию исходного состояния объекта, поэтому мы может вычислять дельта после того, как вызывающий объект мутировал выставленный объект.

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

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

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



Для тех, кто использует AngularJS, существует также прямой метод клонирования или расширения объектов в этой библиотеке.

var destination = angular.copy(source);

или же

angular.copy(source, destination);

Подробнее в documentation на угловую.копию ...




Ответ А.Леви почти завершен, вот мой небольшой вклад: существует способ обработки рекурсивных ссылок , см. Эту строку

if(this[attr]==this) copy[attr] = copy;

Если объект является элементом XML DOM, мы должны использовать cloneNode вместо

if(this.cloneNode) return this.cloneNode(true);

Вдохновленный исчерпывающим исследованием А.Леви и прототипическим подходом Кальвина, я предлагаю это решение:

Object.prototype.clone = function() {
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : {};
  for(var attr in this) {
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  }
  return copy;
}

Date.prototype.clone = function() {
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;
}

Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() {
  return this;
}

См. Также примечание Энди Берка в ответах.




В ES-6 вы можете просто использовать Object.assign (...). Пример:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

Хорошая ссылка здесь: https://googlechrome.github.io/samples/object-assign-es6/




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

var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

Для браузеров / движков, которые в настоящее время не поддерживают Object.create, вы можете использовать этот полиполк:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}



Использование Lodash:

var y = _.clone(x, true);






Ответ Jan Turo answer выше очень близок и может быть лучше всего использовать в браузере из-за проблем совместимости, но это потенциально может вызвать некоторые странные проблемы перечисления. Например, выполнение:

for ( var i in someArray ) { ... }

Присвойте метод clone () i после итерации через элементы массива. Вот адаптация, которая позволяет избежать перечисления и работает с node.js:

Object.defineProperty( Object.prototype, "clone", {
    value: function() {
        if ( this.cloneNode )
        {
            return this.cloneNode( true );
        }

        var copy = this instanceof Array ? [] : {};
        for( var attr in this )
        {
            if ( typeof this[ attr ] == "function" || this[ attr ] == null || !this[ attr ].clone )
            {
                copy[ attr ] = this[ attr ];
            }
            else if ( this[ attr ] == this )
            {
                copy[ attr ] = copy;
            }
            else
            {
                copy[ attr ] = this[ attr ].clone();
            }
        }
        return copy;
    }
});

Object.defineProperty( Date.prototype, "clone", {
    value: function() {
        var copy = new Date();
        copy.setTime( this.getTime() );
        return copy;
    }
});

Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );

Это позволяет избежать использования метода clone (), потому что defineProperty () по умолчанию перечислим значение false.




Я написал свою собственную реализацию. Не уверен, считается ли это лучшим решением:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

Ниже приведена реализация:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}



function clone(src, deep) {

    var toString = Object.prototype.toString;
    if(!src && typeof src != "object"){
        //any non-object ( Boolean, String, Number ), null, undefined, NaN
        return src;
    }

    //Honor native/custom clone methods
    if(src.clone && toString.call(src.clone) == "[object Function]"){
        return src.clone(deep);
    }

    //DOM Elements
    if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
        return src.cloneNode(deep);
    }

    //Date
    if(toString.call(src) == "[object Date]"){
        return new Date(src.getTime());
    }

    //RegExp
    if(toString.call(src) == "[object RegExp]"){
        return new RegExp(src);
    }

    //Function
    if(toString.call(src) == "[object Function]"){
        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });

    }

    var ret, index;
    //Array
    if(toString.call(src) == "[object Array]"){
        //[].slice(0) would soft clone
        ret = src.slice();
        if(deep){
            index = ret.length;
            while(index--){
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }

    return ret;
};



Проконсультируйтесь с http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data для алгоритма W3C «Безопасная передача структурированных данных», который должен быть реализован браузерами для передачи данных, например, веб-работникам. Однако он имеет некоторые ограничения, поскольку он не выполняет функции. Дополнительную информацию см. В https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm , в том числе альтернативный алгоритм в JS, который поможет вам в этом.




Related