Вопрос

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

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

Это было полезно?

Решение

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

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

Еще одним препятствием на пути к элегантному решению является проблема правильной настройки наследования прототипа.Если прототип вашего исходного объекта Object, а затем просто создаем новый общий объект с помощью {} будет работать, но если прототип источника является потомком Object, то вам будет не хватать дополнительных членов из этого прототипа, которые вы пропустили с помощью hasOwnProperty filter или которые были в прототипе, но изначально не были перечислимыми.Одним из решений может быть вызов исходного объекта 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.Последние три типа являются неизменяемыми, поэтому я могу выполнить поверхностное копирование и не беспокоиться об его изменении.Я также предположил, что любые элементы, содержащиеся в 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, но для многих целей этого может быть достаточно, если вы не предполагаете, что он будет работать просто для всего, что вы ему бросите.

Другие советы

Если вы не используете Dates, функции, undefinity или Infinity внутри вашего объекта, очень простой однострочный JSON.parse(JSON.stringify(object)):

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

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

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

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

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

последующие изменения в copiedObject не повлияет на originalObject, и наоборот.

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

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

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

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

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

Пер МДН:

  • Если вам нужен неглубокий текст, используйте Object.assign({}, a)
  • Для «глубокого» копирования используйте JSON.parse(JSON.stringify(a))

Нет необходимости во внешних библиотеках, но вам нужно проверить совместимость браузера в первую очередь.

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

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

  1. Где такое наследование полезно (ага!)
  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

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

Элегантный способ клонировать объект Javascript в одной строке кода.

Ан Object.assign Метод является частью стандарта ECMAScript 2015 (ES6) и делает именно то, что вам нужно.

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

Метод Object.assign() используется для копирования значений всех перечислимых собственных свойств из одного или нескольких исходных объектов в целевой объект.

Читать далее...

А полифилл для поддержки старых браузеров:

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

В большинстве решений в Интернете есть несколько проблем.Поэтому я решил сделать продолжение, в котором расскажу, почему принятый ответ не следует принимать.

исходная ситуация

Я хочу глубокое копирование Javascript Object со всеми своими детьми и их детьми и так далее.Но так как я не обычный разработчик, мой Object имеет нормальный properties, circular structures и даже nested objects.

Итак, давайте создадим circular structure и nested object первый.

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

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

Давайте соберем все воедино в Object названный a.

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

Далее мы хотим скопировать a в переменную с именем b и мутировать его.

var b = a;

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

Вы знаете, что здесь произошло, потому что иначе вы бы даже не остановились на этом важном вопросе.

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"
    }
}

Теперь давайте найдем решение.

JSON

Первая попытка, которую я попробовал, заключалась в использовании JSON.

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

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

Не тратьте на это слишком много времени, вы получите TypeError: Converting circular structure to JSON.

Рекурсивное копирование (принятый «ответ»)

Давайте посмотрим на принятый ответ.

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.");
}

Выглядит хорошо, да?Это рекурсивная копия объекта, которая также обрабатывает другие типы, например Date, но это не было обязательным требованием.

var b = cloneSO(a);

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

Рекурсия и circular structures вместе не получается... RangeError: Maximum call stack size exceeded

собственное решение

После спора с коллегой мой начальник спросил нас, что случилось, и нашел простую причину. решение после некоторого поиска в Google.Это называется Object.create.

var b = Object.create(a);

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

Это решение было добавлено в Javascript некоторое время назад и даже обрабатывает 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"
    }
}

...и вы видите, это не сработало с вложенной структурой внутри.

полифилл для собственного решения

Есть полифилл для Object.create в более старом браузере, как IE 8.Это что-то вроде рекомендации Mozilla, и, конечно, оно не идеально и приводит к той же проблеме, что и собственное решение.

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

var b = clonePF(a);

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

я положил F выходит за рамки, чтобы мы могли посмотреть, что instanceof говорит нам.

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

Та же проблема, что и собственное решение, но немного хуже результат.

лучшее (но не идеальное) решение

Копаясь, нашел аналогичный вопрос(Как в Javascript при выполнении глубокого копирования избежать цикла из-за того, что свойство имеет значение «это»?) к этому, но с гораздо лучшим решением.

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';

И давайте посмотрим на результат...

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

Требования совпадают, но есть еще некоторые более мелкие проблемы, в том числе изменение instance из nested и circ к Object.

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

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

заключение

Последнее решение с использованием рекурсии и кэша, возможно, не самое лучшее, но это настоящий глубокая копия объекта.Он обрабатывает простые properties, circular structures и nested object, но это испортит их экземпляр при клонировании.

jsfiddle

Если вас устраивает неглубокая копия, в библиотеке underscore.js есть клонировать метод.

y = _.clone(x);

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

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

ХОРОШО, представьте, что у вас есть этот объект ниже, и вы хотите его клонировать:

let obj = {a:1, b:2, c:3}; //ES6

или

var obj = {a:1, b:2, c:3}; //ES5

ответ во многом зависит от того, какой ECMAscript вы используете, в ES6+, вы можете просто использовать Object.assign сделать клон:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

или используя оператор распространения, например:

let cloned = {...obj}; //new {a:1, b:2, c:3};

Но если вы используете ES5, вы можете использовать несколько методов, но JSON.stringify, просто убедитесь, что вы не используете для копирования большой объем данных, но во многих случаях это может быть удобно в одну строку, что-то вроде этого:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

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

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

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

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

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

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

Вы можете просто использовать распространять собственность скопировать объект без ссылок.Но будьте осторожны (см. комментарии), «копия» находится на самом нижнем уровне объекта/массива.Вложенные свойства по-прежнему являются ссылками!


Полный клон:

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

// => mutate without references:

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

Клон со ссылками на втором уровне:

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 не поддерживает глубокие клоны.Используйте функцию полезности.Например, Рамда:

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

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

var destination = angular.copy(source);

или

angular.copy(source, destination);

Подробнее в angular.copy документация...

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

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

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

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;
}

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

Вот функция, которую вы можете использовать.

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

Из этой статьи: Как копировать массивы и объекты в Javascript Брайан Хьюсман:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

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

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

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

В ECMAScript 2018

let objClone = { ...obj };

Быть в курсе, что вложенные объекты все еще копируются в качестве ссылки.

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

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();
    };
}

Новый ответ на старый вопрос!Если вам посчастливилось использовать ECMAScript 2016 (ES6) с Распространение синтаксиса, это просто.

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

Это обеспечивает чистый метод для поверхностной копии объекта.Создание глубокой копии, то есть создание новой копии каждого значения в каждом рекурсивно вложенном объекте, требует одного из более сложных решений, описанных выше.

JavaScript продолжает развиваться.

let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

Решение ES6, если вы хотите (поверхностно) клонировать экземпляр класса а не просто объект недвижимости.

Интересует клонирование простых объектов:

JSON.parse(JSON.stringify(json_original));

Источник : Как скопировать объект JavaScript в новую переменную НЕ по ссылке?

Использование Лодаша:

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

Я думаю, что есть простой и работающий ответ.При глубоком копировании есть две проблемы:

  1. Сохраняйте свойства независимыми друг от друга.
  2. И сохраняйте методы клонированного объекта.

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

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

Хотя на этот вопрос есть много ответов, я надеюсь, что этот тоже поможет.

Для глубокого копирования и клонирования выполните JSON.stringify, а затем JSON.parse объекта:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

Я просто хотел добавить ко всему Object.create решения в этом посте о том, что с nodejs это не работает должным образом.

В Firefox результат

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

является

{test:"test"}.

В nodejs это

{}

Это экранизация романа А.Код Леви также обрабатывает клонирование функций и множественных/циклических ссылок. Это означает, что если два свойства в клонируемом дереве являются ссылками на один и тот же объект, то в клонированном дереве объектов эти свойства будут указывать на один и тот же объект. клон указанного объекта.Это также решает проблему циклических зависимостей, которые, если их не обрабатывать, приводят к бесконечному циклу.Сложность алгоритма O(n)

function clone(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") 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; i < obj.length; ++i) {
                copy[i] = cloneRecursive(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


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



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

Несколько быстрых тестов

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
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;
};

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

/*
    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;
}

Ответ Яна Туроня, приведенный выше, очень близок и, возможно, его лучше всего использовать в браузере из-за проблем совместимости, но потенциально он может вызвать некоторые странные проблемы с перечислением.Например, выполнение:

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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top