it-swarm.com.ru

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

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

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

2787
mindeavor

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

1443
A. Levy

Если вы не используете Dates, functions, 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()

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

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

870
heinob

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

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

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

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

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

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

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

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

610
Vitalii Fedorenko

За MDN :

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

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

183
Tareq

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

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

  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

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

128
itpastorn

Элегантный способ клонировать объект 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;
    }
  });
}
117
Eugene Tiurin

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

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

Я хочу глубокое копирование Javascript Object со всеми его потомками и их потомками и так далее. Но так как я не нормальный разработчик, у моего Object есть normal 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
Fabio Poloni

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

y = _.clone(x);

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

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

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

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
46
Alireza

Одним из особенно не элегантных решений является использование 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
38
Kris Walker

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


Полный клон:

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

32
musemind

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

var destination = angular.copy(source);

или же

angular.copy(source, destination);

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

24
Lukas Jelinek

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

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

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

22
Jan Turoň

Из этой статьи: Как копировать массивы и объекты в 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
Calvin

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

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
picardo

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

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

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

18
João Oliveira

В ECMAScript 2018

let objClone = { ...obj };

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

16
Pavan Garre

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

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

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

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

13
Charles Merriam

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

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:

// 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
Rob Evans
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

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

11
flori

Используя Lodash:

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

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

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

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

9
Mohammed Akdim

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

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

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

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

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

8
ConductedClever
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
user1547016

Это адаптация кода А. Леви для обработки клонирования функций и множественных/циклических ссылок - это означает, что если два свойства в клонированном дереве являются ссылками на один и тот же объект, то клонированное дерево объектов будет иметь эти свойства указывают на один и тот же клон ссылочного объекта. Это также решает случай циклических зависимостей, которые, если оставить их необработанными, приводят к бесконечному циклу. Сложность алгоритма составляет 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
Radu Simionescu

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

В Firefox результат

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

является

{test:"test"}.

В nodejs это

{}
6
heinob

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

/*
    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
yazjisuhail

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

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.

5
Andy Burke

Поскольку mindeavor указывало, что клонируемый объект является "буквально сконструированным" объектом, решение может быть просто генерировать объект несколько раз, а не клонировать экземпляр объекта:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
5
Bert Regelink

Консультируйтесь с 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, который поможет вам в этом.

4
user663031

Ниже - моя версия глубокого клонирования, охватывающая функции и обработку циклических ссылок.

https://github.com/radsimu/UaicNlpToolkit/blob/master/Modules/GGS/GGSEngine/src/main/resources/ro/uaic/info/nlptools/ggs/engine/core/jsInitCode.js# L17

4
Radu Simionescu

Используйте Deepcopy от Npm. Работает как в браузере, так и в узле как модуль npm ...

https://www.npmjs.com/package/deepcopy

пусть а = глубокая копия (б)

4
user3071643

Структурированное клонирование

Стандарт HTML включает внутренний структурированный алгоритм клонирования/сериализации , который может создавать глубокие клоны объектов. Он по-прежнему ограничен определенными встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает Dates, RegExps, Карты, Наборы, BLOB-объекты, FileLists, ImageDatas, разреженные массивы, Typed Arrays и, возможно, больше в будущем. , Он также сохраняет ссылки в клонированных данных, что позволяет поддерживать циклические и рекурсивные структуры, которые могут вызвать ошибки для JSON.

Поддержка в Node.js: экспериментальная ????

Модуль v8 в Node.js в настоящее время (по состоянию на Node 11) напрямую предоставляет структурированный API сериализации , но эта функциональность по-прежнему помечена как "экспериментальная" и может быть изменена или удаление в будущих версиях. Если вы используете совместимую версию, клонирование объекта так же просто, как:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

Прямая поддержка в браузерах: возможно, в конце концов? ????

Браузеры в настоящее время не предоставляют прямой интерфейс для алгоритма структурированного клонирования, но глобальная функция structuredClone() обсуждалась в whatwg/html # 793 на GitHub . В настоящее время предлагается использовать его для большинства целей так же просто, как:

const clone = structuredClone(original);

Если это не поставляется, реализации структурированных клонов в браузерах предоставляются только косвенно.

Асинхронный обходной путь: можно использовать. ????

Более простой способ создания структурированного клона с существующими API-интерфейсами состоит в том, чтобы разместить данные через один порт MessageChannels . Другой порт будет генерировать событие message со структурированным клоном присоединенного .data. К сожалению, прослушивание этих событий обязательно асинхронно, а синхронные альтернативы менее практичны.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Пример использования:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Синхронные обходные пути: Ужасно! ????

Нет хороших вариантов для синхронного создания структурированных клонов. Вот пара непрактичных взломов вместо этого.

history.pushState() и history.replaceState() оба создают структурированный клон своего первого аргумента и присваивают это значение history.state. Вы можете использовать это для создания структурированного клона любого объекта, подобного этому:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

Пример использования:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

main();

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

Notification constructor создает структурированный клон связанных с ним данных. Он также пытается отобразить уведомление в браузере для пользователя, но это молча завершится ошибкой, если вы не запросили разрешение на уведомление. Если у вас есть разрешение для других целей, мы немедленно закроем созданное нами уведомление.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

Пример использования:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.close();
  return n.data;
};

main();
3
Jeremy

Клонировать объект на основе "шаблона". Что вы делаете, если вам не нужна точная копия, но вам нужна надежность какой-либо надежной операции клонирования, но вам нужны только клонированные биты или вы хотите убедиться, что можете контролировать существование или формат каждого значения атрибута клонировали?

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

   function isFunction(functionToCheck) {
       var getType = {};
       return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
   }

   function cloneObjectByTemplate(obj, tpl, cloneConstructor) {
       if (typeof cloneConstructor === "undefined") {
           cloneConstructor = false;
       }
       if (obj == null || typeof (obj) != 'object') return obj;

       //if we have an array, work through it's contents and apply the template to each item...
       if (Array.isArray(obj)) {
           var ret = [];
           for (var i = 0; i < obj.length; i++) {
               ret.Push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor));
           }
           return ret;
       }

       //otherwise we have an object...
       //var temp:any = {}; // obj.constructor(); // we can't call obj.constructor because TypeScript defines this, so if we are dealing with a TypeScript object it might reset values.
       var temp = cloneConstructor ? new obj.constructor() : {};

       for (var key in tpl) {
           //if we are provided with a function to determine the value of this property, call it...
           if (isFunction(tpl[key])) {
               temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value
           } else {
               //if our object has this property...
               if (obj[key] != undefined) {
                   if (Array.isArray(obj[key])) {
                       temp[key] = [];
                       for (var i = 0; i < obj[key].length; i++) {
                           temp[key].Push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor));
                       }
                   } else {
                       temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor);
                   }
               }
           }
       }

       return temp;
   }

Простой способ назвать это было бы так:

var source = {
       a: "whatever",
       b: {
           x: "yeah",
           y: "haha"
       }
   };
   var template = {
       a: true, //we want to clone "a"
       b: {
           x: true //we want to clone "b.x" too
       }
   }; 
   var destination = cloneObjectByTemplate(source, template);

Если вы хотите использовать функцию, чтобы убедиться, что атрибут возвращен, или чтобы убедиться, что это определенный тип, используйте такой шаблон. Вместо использования {ID: true} мы предоставляем функцию, которая все еще просто копирует атрибут ID исходного объекта, но гарантирует, что это число, даже если его нет в исходном объекте.

 var template = {
    ID: function (srcObj) {
        if(srcObj.ID == undefined){ return -1; }
        return parseInt(srcObj.ID.toString());
    }
}

Массивы отлично клонируются, но если вы хотите, вы можете иметь свою собственную функцию, которая обрабатывает и эти отдельные атрибуты, и делать что-то особенное, например:

 var template = {
    tags: function (srcObj) {
        var tags = [];
        if (process.tags != undefined) {
            for (var i = 0; i < process.tags.length; i++) {

                tags.Push(cloneObjectByTemplate(
                  srcObj.tags[i],
                  { a : true, b : true } //another template for each item in the array
                );
            }
        }
        return tags;
    }
 }

Таким образом, в приведенном выше примере наш шаблон просто копирует атрибут 'tags' исходного объекта, если он существует (предполагается, что он является массивом), и для каждого элемента в этом массиве вызывается функция clone, чтобы индивидуально клонировать его на основе второй шаблон, который просто копирует атрибуты "a" и "b" каждого из этих элементов тега.

Если вы забираете объекты в узел и выходите из него и хотите контролировать, какие атрибуты этих объектов клонируются, это отличный способ управлять этим в файле node.js, и код также работает в браузере.

Вот пример его использования: http://jsfiddle.net/hjchyLt1/

3
Action Dan

(Следующее было в основном объединением @ Мачей Буковски , @ А. Леви , @ Ян Туро , @ Реду = ответы, и комментарии @ 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
        ).call(object)

        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's 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's prototype === boy0's:",
  Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)

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

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

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

  1. Условно добавьте реквизит к объекту
3
ooo

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

Сначала создайте функцию, которая возвращает объект

function template() {
  return {
    values: [1, 2, 3],
    nest: {x: {a: "a", b: "b"}, y: 100}
  };
}

Затем создайте простую функцию мелкого копирования

function copy(a, b) {
  Object.keys(b).forEach(function(key) {
    a[key] = b[key];
  });
}

Создайте новый объект и скопируйте на него свойства шаблона.

var newObject = {}; 
copy(newObject, template());

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

var newObject = template();

Теперь, когда у вас есть новый объект, протестируйте его свойства:

console.log(Object.keys(newObject));

Это отображает:

["values", "nest"]

Да, это собственные свойства newObject, а не ссылки на свойства другого объекта. Давайте просто проверим:

console.log(newObject.nest.x.b);

Это отображает:

"b"

NewObject получил все свойства объекта шаблона, но свободен от какой-либо цепочки зависимостей.

http://jsbin.com/ISUTIpoC/1/edit?js,console

Я добавил этот пример, чтобы стимулировать некоторые дебаты, поэтому, пожалуйста, добавьте несколько комментариев :)

3
d13

Согласно Руководство по стилю JavaScript для Airbnb с 404 участниками:

Предпочитайте оператор растягивания объекта над Object.assign для мелкого копирования объектов Используйте оператор rest объекта, чтобы получить новый объект с опущенными определенными свойствами.

// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

Также я хотел бы предупредить вас, что даже несмотря на то, что Airbnb вряд ли рекомендует подход оператора распространения объекта. Помните, что Microsoft Edge по-прежнему не поддерживает эту функцию 2018 года.

ES2016 + Compat table >>

3
Zsolt Gulyás

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

А недооценка WeakMap приводит к проблеме циклов, в которой хранение пар ссылок на старый и новый объект может помочь нам довольно легко воссоздать все дерево.

Я предотвратил глубокое клонирование элементов DOM, возможно, вы не хотите клонировать всю страницу :)

function deepCopy(object) {
    const cache = new WeakMap(); // Map of old - new references

    function copy(obj) {
        if (typeof obj !== 'object' ||
            obj === null ||
            obj instanceof HTMLElement
        )
            return obj; // primitive value or HTMLElement

        if (obj instanceof Date) 
            return new Date().setTime(obj.getTime());

        if (obj instanceof RegExp) 
            return new RegExp(obj.source, obj.flags);

        if (cache.has(obj)) 
            return cache.get(obj);

        const result = obj instanceof Array ? [] : {};

        cache.set(obj, result); // store reference to object before the recursive starts

        if (obj instanceof Array) {
            for(const o of obj) {
                 result.Push(copy(o));
            }
            return result;
        }

        const keys = Object.keys(obj); 

        for (const key of keys)
            result[key] = copy(obj[key]);

        return result;
    }

    return copy(object);
}

Некоторые тесты:

// #1
const obj1 = { };
const obj2 = { };
obj1.obj2 = obj2;
obj2.obj1 = obj1; // Trivial circular reference

var copy = deepCopy(obj1);
copy == obj1 // false
copy.obj2 === obj1.obj2 // false
copy.obj2.obj1.obj2 // and so on - no error (correctly cloned).

// #2
const obj = { x: 0 }
const clone = deepCopy({ a: obj, b: obj });
clone.a == clone.b // true

// #3
const arr = [];
arr[0] = arr; // A little bit weird but who cares
clone = deepCopy(arr)
clone == arr // false;
clone[0][0][0][0] == clone // true;

ПРИМЕЧАНИЕ. Я использую константы for for, оператор => и WeakMaps для создания более существенного кода. Этот синтаксис (ES6) поддерживается современными браузерами

2
Maciej Bukowski

Я попробовал это в случае скалярного объекта, и он работает для меня:

function binder(i) {
  return function () {
    return i;
  };
}

a=1;
b=binder(a)(); // copy value of a into b

alert(++a); // 2
alert(b); // still 1

С уважением.

2
John Sonderson

Для глубокого копирования я использую:

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

Использование по умолчанию (исторически специфично для nodejs, но теперь доступно из браузера благодаря современному JS):

import defaults from 'object.defaults';

const myCopy = defaults({}, myObject);
1
aberaud

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

Если существуют циклические зависимости (то есть, два подобъекта связаны друг с другом), вы как бы ввернуты как есть (с теоретической точки зрения) нет способа элегантно решить эту проблему .

1
Daniel Lew

Итак, это может быть самым лучшим вариантом для мелкого копирования. Если следовать много примеров с использованием assign, но он также сохраняет наследование и прототип. Это также очень просто и работает для большинства массивов и объектов, за исключением тех, которые имеют требования конструктора или свойства только для чтения. Но это означает, что он с треском проваливается для версий примитивов TypedArrays, RegExp, Date, Maps, Sets и Object (Boolean, String и т.д.).

function copy ( a ) { return Object.assign( new a.constructor, a ) }

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

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

результаты из базовых встроенных объектов и массивов ...

> a = { a: 'A', b: 'B', c: 'C', d: 'D' }
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> b = copy( a )
{ a: 'A', b: 'B', c: 'C', d: 'D' }
> a = [1,2,3,4]
[ 1, 2, 3, 4 ]
> b = copy( a )
[ 1, 2, 3, 4 ]

И терпит неудачу из-за средних значений get/setters, конструктора, требующих аргументов или свойств только для чтения, и грешит против отца.

> a = /\w+/g
/\w+/g
> b = copy( a )  // fails because source and flags are read-only
/(?:)/
> a = new Date ( '1/1/2001' )
2000-12-31T16:00:00.000Z
> b = copy( a )  // fails because Date using methods to get and set things
2017-02-04T14:44:13.990Z
> a = new Boolean( true )
[Boolean: true]
> b = copy( a )  // fails because of of sins against the father
[Boolean: false]
> a = new Number( 37 )
[Number: 37]
> b = copy( a )  // fails because of of sins against the father
[Number: 0]
> a = new String( 'four score and seven years ago our four fathers' )
[String: 'four score and seven years ago our four fathers']
> b = copy( a )  // fails because of of sins against the father
{ [String: ''] '0': 'f', '1': 'o', '2': 'u', '3': 'r', '4': ' ', '5': 's', '6': 'c', '7': 'o', '8': 'r', '9': 'e', '10': ' ', '11': 'a', '12': 'n', '13': 'd', '14': ' ', '15': 's', '16': 'e', '17': 'v', '18': 'e', '19': 'n', '20': ' ', '21': 'y', '22': 'e', '23': 'a', '24': 'r', '25': 's', '26': ' ', '27': 'a', '28': 'g', '29': 'o', '30': ' ', '31': 'o', '32': 'u', '33': 'r', '34': ' ', '35': 'f', '36': 'o', '37': 'u', '38': 'r', '39': ' ', '40': 'f', '41': 'a', '42': 't', '43': 'h', '44': 'e', '45': 'r', '46': 's' } 
1
Erich Horn

Вот современное решение, в котором нет ошибок Object.assign() (не копируется по ссылке):

const cloneObj = (obj) => {
    return Object.keys(obj).reduce((dolly, key) => {
        dolly[key] = (obj[key].constructor === Object) ?
            cloneObj(obj[key]) :
            obj[key];
        return dolly;
    }, {});
};
1
ryanpcmcquen

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

const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

const returnedTarget = Object.assign(target, source);

console.log(target);
// expected output: Object { a: 1, b: 4, c: 5 }

console.log(returnedTarget);
// expected output: Object { a: 1, b: 4, c: 5 }

Синтаксис

Object.assign(target, ...sources)

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

поэтому он будет вызывать геттеры и сеттеры. Поэтому он присваивает свойства вместо простого копирования или определения новых свойств. Это может сделать его непригодным для объединения новых свойств в прототип, если источники слияния содержат геттеры. Для копирования определений свойств, включая их перечислимость, в прототипы следует использовать Object.getOwnPropertyDescriptor() и Object.defineProperty().

Свойства String и Symbol копируются.

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

Обратите внимание, что Object.assign() не генерирует исходные значения null или undefined.

1
Ashish

Чтобы обеспечить лучшее понимание копирования объектов, этот иллюстративный jsbin может иметь значение

class base {
  get under(){return true}
}

class a extends base {}

const b = {
  get b1(){return true},
  b: true
}

console.log('Object assign')
let t1 = Object.create(b)
t1.x = true
const c = Object.assign(t1, new a())
console.log(c.b1 ? 'prop value copied': 'prop value gone')
console.log(c.x ? 'assigned value copied': 'assigned value gone')
console.log(c.under ? 'inheritance ok': 'inheritance gone')
console.log(c.b1 ? 'get value unchanged' : 'get value lost')
c.b1 = false
console.log(c.b1? 'get unchanged' : 'get lost')
console.log('-----------------------------------')
console.log('Object assign  - order swopped')
t1 = Object.create(b)
t1.x = true
const d = Object.assign(new a(), t1)
console.log(d.b1 ? 'prop value copied': 'prop value gone')
console.log(d.x ? 'assigned value copied': 'assigned value gone')
console.log(d.under ? 'inheritance n/a': 'inheritance gone')
console.log(d.b1 ? 'get value copied' : 'get value lost')
d.b1 = false
console.log(d.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e = { ...t1, ...t2 }
console.log(e.b1 ? 'prop value copied': 'prop value gone')
console.log(e.x ? 'assigned value copied': 'assigned value gone')
console.log(e.under ? 'inheritance ok': 'inheritance gone')
console.log(e.b1 ? 'get value copied' : 'get value lost')
e.b1 = false
console.log(e.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator on getPrototypeOf')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e1 = { ...Object.getPrototypeOf(t1), ...Object.getPrototypeOf(t2) }
console.log(e1.b1 ? 'prop value copied': 'prop value gone')
console.log(e1.x ? 'assigned value copied': 'assigned value gone')
console.log(e1.under ? 'inheritance ok': 'inheritance gone')
console.log(e1.b1 ? 'get value copied' : 'get value lost')
e1.b1 = false
console.log(e1.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('keys, defineProperty, getOwnPropertyDescriptor')
f = Object.create(b)
t2 = new a()
f.x = 'a'
Object.keys(t2).forEach(key=> {
  Object.defineProperty(f,key,Object.getOwnPropertyDescriptor(t2, key))
})
console.log(f.b1 ? 'prop value copied': 'prop value gone')
console.log(f.x ? 'assigned value copied': 'assigned value gone')
console.log(f.under ? 'inheritance ok': 'inheritance gone')
console.log(f.b1 ? 'get value copied' : 'get value lost')
f.b1 = false
console.log(f.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('defineProperties, getOwnPropertyDescriptors')
let g = Object.create(b)
t2 = new a()
g.x = 'a'
Object.defineProperties(g,Object.getOwnPropertyDescriptors(t2))
console.log(g.b1 ? 'prop value copied': 'prop value gone')
console.log(g.x ? 'assigned value copied': 'assigned value gone')
console.log(g.under ? 'inheritance ok': 'inheritance gone')
console.log(g.b1 ? 'get value copied' : 'get value lost')
g.b1 = false
console.log(g.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
1
TrevTheDev

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

Я использую функцию toType () для явного возврата типа объекта. У меня также есть моя собственная функция copyObj (), которая довольно похожа по логике и отвечает на все три случая Object (), Array () и Date ().

Я запускаю его в NodeJS.

НЕ ПРОВЕРЕНО, ДА.

// Returns true, if one of the parent's children is the target.
// This is useful, for avoiding copyObj() through an infinite loop!
function isChild(target, parent) {
  if (toType(parent) == '[object Object]') {
    for (var name in parent) {
      var curProperty = parent[name];

      // Direct child.
      if (curProperty = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') {
        if (isChild(target, curProperty)) return true;
      }
    }
  } else if (toType(parent) == '[object Array]') {
    for (var i=0; i < parent.length; i++) {
      var curItem = parent[i];

      // Direct child.
      if (curItem = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') {
        if (isChild(target, curItem)) return true;
      }
    }
  }

  return false;     // Not the target.
}
0
James Koss

Я не знаю, в каких случаях это не работает, но я получил копию массива. Я думаю, что это мило :) Надеюсь, это помогает

copiedArr = origArr.filter(function(x){return true})
0
knowingpark

Для обработки круговых объектов, которые JSON.stringify не может обработать, вы можете добавить библиотеку с именем JSOG , которая сериализует и десериализует произвольные графы в формат JSON.

var clone = JSOG.parse(JSOG.stringify(original));

Также было бы интересно попробовать залатать JSOG для клонирования с помощью этого трюка (на данный момент не хватает времени, но если кто-то захочет дать ему шанс ...):

Сериализация простой функции:

foo.f = function(a) { return a }
var stringForm = foo.f.toString() // "function (a) { return a }"

Десериализовать функцию:

eval("foo.f = " + stringForm)

Потребуются некоторые соглашения (вероятно, в имени свойства) для идентификации функций по сравнению с обычными строками (возможно, @func_f).

Конечно, если функция вызывает вторую функцию, вторая функция должна существовать так же, как и для оригинала.

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

Отказ от ответственности: Я не проверял скорость JSOG stringify/parse против JSON stringify/parse, но он работает с простыми (круговыми) объектами, которые я тестировал это с.

0
Gus

Если у вас есть объект с функциями, вы можете сделать это с помощью JSONfn, см. http://www.eslinstructor.net/jsonfn/ .

var obj= {
    name:'Marvin',
    getName :  function(){
      return this.name;
    }
}
var cobj = JSONfn.parse(JSONfn.stringify(obj));
0
basis

Вы можете клонировать свой объект без модификации родительского объекта -

    /** [Object Extend]*/
    ( typeof Object.extend === 'function' ? undefined : ( Object.extend = function ( destination, source ) {
        for ( var property in source )
            destination[property] = source[property];
        return destination;
    } ) );
    /** [/Object Extend]*/
    /** [Object clone]*/
    ( typeof Object.clone === 'function' ? undefined : ( Object.clone = function ( object ) {
        return this.extend( {}, object );
    } ) );
    /** [/Object clone]*/

    let myObj = {
        a:1, b:2, c:3, d:{
            a:1, b:2, c:3
        }
    };

    let clone = Object.clone( myObj );

    clone.a = 10;

    console.log('clone.a==>', clone.a); //==> 10

    console.log('myObj.a==>', myObj.a); //==> 1 // object not modified here

    let clone2 = Object.clone( clone );

    clone2.a = 20;

    console.log('clone2.a==>', clone2.a); //==> 20

    console.log('clone.a==>', clone.a); //==> 10 // object not modified here
0
RKTUXYN

Я пришел на эту страницу из-за того же вопроса, но я не использую JQuery, и ни один из методов-клонов не работал для моих собственных объектов.

Я знаю, что мой ответ не слишком сильно связан с этим вопросом, потому что это другой подход. Вместо использования функций-клонов я использую функцию создания. Это сработало для меня в следующих (к сожалению ограничивающих) целях:

  1. Я использую в основном JSP-сгенерированный JSP
  2. В начале я знаю, какой объект должен быть сгенерирован (в моем случае это информация из базы данных, которая выбирается один раз, и ее нужно чаще развертывать в JS).

Сначала я определил свои объекты следующим образом:

var obj= new Object();
obj.Type='Row';
obj.ID=1;
obj.Value='Blah blah';

Теперь я переехал все как:

function getObjSelektor(id_nummer,selected){
var obj = document.createElement("select");
obj.setAttribute("id","Selektor_"+id_nummer);
obj.setAttribute("name","Selektor");
obj.setAttribute("size","1");

var obj_opt_1 = document.createElement("option");
obj_opt_1.setAttribute("value","1");
if(1==selected)
    posopval_opt_1.setAttribute("selected","selected");
obj_opt_1.innerHTML="Blah blah";
obj.appendChild(obj_opt_1);

var obj_opt_2 = document.createElement("option");
obj_opt_2.setAttribute("value","2");
if(2==selected)
    obj_opt_2.setAttribute("selected","selected");
obj_opt_2.innerHTML="2nd Row";
obj.appendChild(obj_opt_2);

...

return obj;
}

И вызвать функцию в обычном коде:

myDiv.getObjSelektor(getObjSelektor(anotherObject.ID));

Как уже говорилось, это другой подход, который решил мою проблему для моих целей.

0
Qohelet
//
// creates 'clone' method on context object
//
//  var 
//     clon = Object.clone( anyValue );
//
!((function (propertyName, definition) {
    this[propertyName] = definition();
}).call(
    Object,
    "clone",
    function () {
        function isfn(fn) {
            return typeof fn === "function";
        }

        function isobj(o) {
            return o === Object(o);
        }

        function isarray(o) {
            return Object.prototype.toString.call(o) === "[object Array]";
        }

        function fnclon(fn) {
            return function () {
                fn.apply(this, arguments);
            };
        }

        function owns(obj, p) {
            return obj.hasOwnProperty(p);
        }

        function isemptyobj(obj) {
            for (var p in obj) {
                return false;
            }
            return true;
        }

        function isObject(o) {
            return Object.prototype.toString.call(o) === "[object Object]";
        }
        return function (input) {
            if (isfn(input)) {
                return fnclon(input);
            } else if (isobj(input)) {
                var cloned = {};
                for (var p in input) {
                    owns(Object.prototype, p)
                    || (
                        isfn(input[p])
                        && ( cloned[p] = function () { return input[p].apply(input, arguments); } )
                        || ( cloned[p] = input[p] )
                    );
                }
                if (isarray(input)) {
                    cloned.length = input.length;
                    "concat every filter forEach indexOf join lastIndexOf map pop Push reduce reduceRight reverse shift slice some sort splice toLocaleString toString unshift"
                    .split(" ")
                    .forEach(
                      function (methodName) {
                        isfn( Array.prototype[methodName] )
                        && (
                            cloned[methodName] =
                            function () {
                                return Array.prototype[methodName].apply(cloned, arguments);
                            }
                        );
                      }
                    );
                }
                return isemptyobj(cloned)
                       ? (
                          isObject(input)
                          ? cloned
                          : input
                        )
                       : cloned;
            } else {
                return input;
            }
        };
    }
));
//
0
public override

Из Apple Coding Guidelines :

// Create an inner object with a variable x whose default
// value is 3.
function innerObj()
{
        this.x = 3;
}
innerObj.prototype.clone = function() {
    var temp = new innerObj();
    for (myvar in this) {
        // this object does not contain any objects, so
        // use the lightweight copy code.
        temp[myvar] = this[myvar];
    }
    return temp;
}

// Create an outer object with a variable y whose default
// value is 77.
function outerObj()
{
        // The outer object contains an inner object.  Allocate it here.
        this.inner = new innerObj();
        this.y = 77;
}
outerObj.prototype.clone = function() {
    var temp = new outerObj();
    for (myvar in this) {
        if (this[myvar].clone) {
            // This variable contains an object with a
            // clone operator.  Call it to create a copy.
            temp[myvar] = this[myvar].clone();
        } else {
            // This variable contains a scalar value,
            // a string value, or an object with no
            // clone function.  Assign it directly.
            temp[myvar] = this[myvar];
        }
    }
    return temp;
}

// Allocate an outer object and assign non-default values to variables in
// both the outer and inner objects.
outer = new outerObj;
outer.inner.x = 4;
outer.y = 16;

// Clone the outer object (which, in turn, clones the inner object).
newouter = outer.clone();

// Verify that both values were copied.
alert('inner x is '+newouter.inner.x); // prints 4
alert('y is '+newouter.y); // prints 16

Стив

0
Steve Harrison

Так же, как эта ссылка говорит, используйте этот код:
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

0
Pouria Moosavi

Если ваш объект является классом (например, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes ):

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

Затем в copiedObject у вас есть глубоко скопированный экземпляр класса originalObject со всеми его методами.

0
j rdl

Если вы используете TypeScript, вам нужно поддерживать старые веб-браузеры (и поэтому не можете использовать Object.assign) и не используете библиотеку со встроенным методом клонирования, вы можете сделать себя помощником combine в нескольких строках кода. Он объединяет объекты, и если у вас есть только один, просто клонируйте его.

/** Creates a new object that combines the properties of the specified objects. */
function combine(...objs: {}[]) {
    const combined = {};
    objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p]));
    return combined;
}
0
Edward Brey

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

Проблема в том, что <element> имеет атрибуты parent и child, которые ссылаются на другие элементы со значениями parent и child, которые указывают на исходный код <element>, вызывая либо бесконечная рекурсивная или циклическая избыточность).

Если ваш объект является чем-то безопасным и простым, как

{
    '123':456
}

... тогда любой другой ответ здесь, вероятно, будет работать.

Но если у вас есть ...

{
    '123':<reactJSComponent>,
    '456':document.createElement('div'),
}

... тогда вам нужно что-то вроде этого:

    // cloneVariable() : Clone variable, return null for elements or components.
var cloneVariable = function (args) {
    const variable = args.variable;

    if(variable === null) {
            return null;
    }

    if(typeof(variable) === 'object') {
            if(variable instanceof HTMLElement || variable.nodeType > 0) {
                    return null;
            }

            if(Array.isArray(variable)) {
                    var arrayclone = [];

                    variable.forEach((element) => {
                            arrayclone.Push(cloneVariable({'variable':element}));
                    });

                    return arrayclone;
            }

            var objectclone = {};

            Object.keys(variable).forEach((field) => {
                    objectclone[field] = cloneVariable({'variable':variable[field]});
            });

            return objectclone;
    }

    return variable;
}
0
HoldOffHunger

В моем коде я часто определяю функцию (_) для обработки копий, чтобы я мог передавать "по значению" функциям. Этот код создает глубокую копию, но сохраняет наследование. Он также отслеживает вложенные копии, так что объекты со ссылками на себя можно копировать без бесконечного цикла. Не стесняйтесь использовать его.

Это может быть не самый элегантный, но это еще не подвело меня.

_ = function(oReferance) {
  var aReferances = new Array();
  var getPrototypeOf = function(oObject) {
    if(typeof(Object.getPrototypeOf)!=="undefined") return Object.getPrototypeOf(oObject);
    var oTest = new Object();
    if(typeof(oObject.__proto__)!=="undefined"&&typeof(oTest.__proto__)!=="undefined"&&oTest.__proto__===Object.prototype) return oObject.__proto__;
    if(typeof(oObject.constructor)!=="undefined"&&typeof(oTest.constructor)!=="undefined"&&oTest.constructor===Object&&typeof(oObject.constructor.prototype)!=="undefined") return oObject.constructor.prototype;
    return Object.prototype;
  };
  var recursiveCopy = function(oSource) {
    if(typeof(oSource)!=="object") return oSource;
    if(oSource===null) return null;
    for(var i=0;i<aReferances.length;i++) if(aReferances[i][0]===oSource) return aReferances[i][1];
    var Copy = new Function();
    Copy.prototype = getPrototypeOf(oSource);
    var oCopy = new Copy();
    aReferances.Push([oSource,oCopy]);
    for(sPropertyName in oSource) if(oSource.hasOwnProperty(sPropertyName)) oCopy[sPropertyName] = recursiveCopy(oSource[sPropertyName]);
    return oCopy;
  };
  return recursiveCopy(oReferance);
};

// Examples:
Wigit = function(){};
Wigit.prototype.bInThePrototype = true;
A = new Wigit();
A.nCoolNumber = 7;
B = _(A);
B.nCoolNumber = 8; // A.nCoolNumber is still 7
B.bInThePrototype // true
B instanceof Wigit // true
0
Alec