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

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

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

+2812
08 апр. '09 в 3:01
источник поделиться
66 ответов
  • 1
  • 2
  • 3

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

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

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

+1454
08 апр. '09 в 5:58
источник

Если вы не используете Date s, функции, undefined или 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()

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

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

+878
03 июн. '12 в 9:36
источник

С jQuery вы можете копировать с расширением:

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

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

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

var copiedObject = jQuery.extend(true, {}, originalObject)
+753
02 мар. '11 в 6:36
источник

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

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

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

+616
05 мая '15 в 1:46
источник

За MDN:

  • Если вы хотите неглубокую копию, используйте Object.assign({}, a)
  • Для "глубокой" копии используйте JSON.parse(JSON.stringify(a))

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

+186
07 нояб. '16 в 17:14
источник

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

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

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

Пример:

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

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

+129
19 мар. '12 в 15:17
источник

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

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

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

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

Подробнее...

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

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;
    }
  });
}
+118
15 дек. '15 в 16:42
источник

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

стартовая ситуация

Я хочу глубоко скопировать 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, когда я выполняю глубокое копирование, как избежать цикла из-за свойства "this"?) На этот, но с более лучшим решением.

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

+77
09 июл. '14 в 8:37
источник

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

y = _.clone(x);

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

copiedObject = _.extend({},originalObject);
+76
13 июн. '12 в 23:44
источник

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

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

или же

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

ответ в основном depeneds, на котором 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
+47
06 июл. '17 в 13:50
источник

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

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

Эта методология была идеальна для моего варианта использования, поскольку я храню JOB-капли в хранилище ключей и когда они отображаются как объекты в 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
+38
29 окт. '09 в 20:24
источник

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


Полный клон:

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

+34
13 апр. '16 в 15:43
источник

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

var destination = angular.copy(source);

или

angular.copy(source, destination);

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

+24
03 сент. '14 в 19:08
источник

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

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

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

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

Вдохновленный исчерпывающим исследованием A.Levy и прототипом Calvin, я предлагаю это решение:

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

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

+22
26 янв. '12 в 18:12
источник

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

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;
}
+19
08 апр. '09 в 3:16
источник

Из этой статьи: Как скопировать массивы и объекты в 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;
};
+19
08 апр. '09 в 3:13
источник

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

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

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

+18
13 апр. '17 в 20:17
источник

В ECMAScript 2018

let objClone = { ...obj };

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

+17
03 авг. '18 в 15:55
источник

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

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

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

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

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

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}
+13
26 мая '12 в 13:45
источник

Новый ответ на старый вопрос! Если у вас есть удовольствие от использования ECMAScript 2016 (ES6) с Spread Syntax, это легко.

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

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

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

+13
16 дек. '16 в 11:34
источник
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

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

+11
12 мая '17 в 10:49
источник

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

var y = _.clone(x, true);
+10
13 дек. '12 в 0:05
источник

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

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

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

+9
27 авг. '15 в 9:20
источник

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

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

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

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

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

+8
12 янв. '19 в 7:04
источник
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;
};
+6
23 июл. '12 в 21:43
источник

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

В Firefox результат

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

есть

{test:"test"}.

В nodejs это

{}
+6
03 июн. '12 в 9:29
источник

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

/*
    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;
}
+6
12 нояб. '14 в 1:25
источник

Это адаптация кода А. Леви для обработки клонирования функций и множественных/циклических ссылок. Это означает, что если два свойства в клонированном дереве являются ссылками одного и того же объекта, клонированное дерево объектов будут иметь эти свойства, указывающие на один и тот же клон ссылочного объекта. Это также решает случай циклических зависимостей, которые, если их оставить необработанными, приводят к бесконечной петле. Сложность алгоритма 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));
+6
12 июл. '12 в 23:15
источник

(Ниже, в основном, интеграция @Maciej Буковски, @А. Леви, @Ян Турон, @REDU ответы и @LeviRoberts, @RobG комментарии, большое спасибо им !!!)

Глубокая копия? - ДА! (в основном);
Мелкая копия? - НЕТ! (кроме Proxy).

Я искренне приветствую всех желающих протестировать clone().
Кроме того, defineProp() предназначен для простого и быстрого (пере) определения или копирования любого типа дескриптора.

функция

function clone(object) {
  /*
    Deep copy objects by value rather than by reference,
    exception: 'Proxy'
  */

  const seen = new WeakMap()

  return (function clone(object) {
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) {
      case Object:
      case Array:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        const fnStr = String(object)

        _object = new Function("return " +
          (/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr)
            ? "function " : ""
          ) + fnStr
        )()

        Object.defineProperties(_object,
          Object.getOwnPropertyDescriptors(object)
        )
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) {
          //                              // Stem from:
          case "[object Function]":       // 'class'
          case "[object Undefined]":      // 'Object.create(null)'
            _object = cloneObject(object)
            break

          default:                        // 'Proxy'
            _object = object
        }
    }

    return _object


    function cloneObject(object) {
      if (seen.has(object)) return seen.get(object) /*
      —— Handle recursive references (circular structures) */

      const _object = Array.isArray(object)
        ? []
        : Object.create(Object.getPrototypeOf(object)) /*
          —— Assign [[Prototype]] for inheritance */

      seen.set(object, _object) /*
      —— Make '_object' the associative mirror of 'object' */

      Reflect.ownKeys(object).forEach(key =>
        defineProp(_object, key, { value: clone(object[key]) }, object)
      )

      return _object
    }
  })(object)
}


function defineProp(object, key, descriptor = {}, copyFrom = {}) {
  const prevDesc = Object.getOwnPropertyDescriptor(object, key)
    || { configurable: true, writable: true }
    , copyDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
      || { configurable: true, writable: true } // Custom…
      || {} // …or left to native default settings

  const { configurable: _configurable, writable: _writable } = prevDesc
    , test = _writable === undefined
      ? _configurable // Can redefine property
      : _configurable && _writable // Can assign to property

  if (!test || arguments.length <= 2) return test;

  ["get", "set", "value", "writable", "enumerable", "configurable"]
    .forEach(k =>
      descriptor[k] === undefined && (descriptor[k] = copyDesc[k])
    )

  const { get, set, value, writable, enumerable, configurable }
    = descriptor

  return Object.defineProperty(object, key, {
    enumerable, configurable, ...get || set
      ? { get, set } // Accessor descriptor
      : { value, writable } // Data descriptor
  })
}

// Тесты

"use strict"
const obj0 = {

  u: undefined,

  nul: null,

  t: true,

  n: 9,

  str1: "string",

  str2: "",

  sym: Symbol("symbol"),

  [Symbol("e")]: Math.E,

  f: {
    getAccessorStr(object) {
      return []
        .concat(...
          Object.values(Object.getOwnPropertyDescriptors(object))
            .filter(desc => desc.writable === undefined)
            .map(desc => Object.values(desc))
        )
        .filter(prop => typeof prop === "function")
        .map(String)
    },
    f0: function f0() { },
    f1: function () { },
    f2: a => a / (a + 1),
    f3: () => 0,
    f4(params) { return param => param + params },
    f5: (a, b) => ({ c = 0 } = {}) => a + b + c
  },

  o: {
    n: 0,
    o: {
      f: function (...args) { }
    }
  },

  arr: [[0], [1, 2]],

  d: new Date(),

  get g() { return 0 }
}

defineProp(obj0, "s", {
  set(v) { this._s = v }
})
defineProp(obj0.arr, "tint", {
  value: { is: "non-enumerable" }
})
obj0.arr[0].name = "nested array"


let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0) { return a + b }
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s", {
  set(v) { this._s = v + 1 }
})

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Routinely")

console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()

console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()

console.log("obj0\n ",
  ".arr.tint:", obj0.arr.tint, "\n ",
  ".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
  ".arr.tint:", obj1.arr.tint, "\n ",
  ".arr[0].name:", obj1.arr[0].name
)
console.log()

console.log("Accessor-type descriptor\n ",
  "of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
  "of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
  "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
  "  → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)

console.log("—— obj0 has not been interfered.")

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Circular structures")

obj0.o.r = {}
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr

obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)

console.log("Clear obj0 recursion:",
  obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
  "obj0\n ",
  ".o.r:", obj0.o.r, "\n ",
  ".arr:", obj0.arr
)
console.log(
  "obj1\n ",
  ".o.r:", obj1.o.r, "\n ",
  ".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")


console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Classes")

class Person {
  constructor(name) {
    this.name = name
  }
}

class Boy extends Person { }
Boy.prototype.sex = "M"

const boy0 = new Boy
boy0.hobby = { sport: "spaceflight" }

const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"

boy0.name = "one"
boy1.name = "neo"

console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1 prototype === boy0's:",
  Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)

Рекомендации

  1. Object.create() | MDN
  2. Object.defineProperties() | MDN
  3. Перечислимость и владение недвижимостью | MDN
  4. TypeError: значение циклического объекта | MDN

Используемые языковые трюки

  1. Условно добавьте реквизит к объекту
+5
12 дек. '18 в 6:48
источник

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

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

Назначит метод clone() я после итерации через элементы массива. Здесь адаптация, которая позволяет избежать перечисления и работает с 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.

+5
30 мар. '12 в 6:03
источник
  • 1
  • 2
  • 3

Посмотрите другие вопросы по меткам или Задайте вопрос