Каков наиболее эффективный способ глубокого клонирования объекта в JavaScript?

StackOverflow https://stackoverflow.com/questions/122102

  •  02-07-2019
  •  | 
  •  

Вопрос

Каков наиболее эффективный способ клонирования объекта JavaScript?я видел obj = eval(uneval(o)); используется, но это нестандартно и поддерживается только Firefox.

Я делал такие вещи, как obj = JSON.parse(JSON.stringify(o)); но сомневаюсь в эффективности.

Я также видел функции рекурсивного копирования с различными недостатками.
Я удивлен, что канонического решения не существует.

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

Решение

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

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

Это называется «структурированное клонирование», экспериментально работает в Node 11 и более поздних версиях и, надеюсь, появится в браузерах.Видеть этот ответ Больше подробностей.

Быстрое клонирование с потерей данных — JSON.parse/stringify

Если вы не используете Dateфункции, undefined, Infinity, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, разреженные массивы, типизированные массивы или другие сложные типы внутри вашего объекта, очень простой один лайнер для глубокого клонирования объекта:

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'
  re: /.*/,  // lost
}
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()

Видеть Ответ Корбана для эталонов.

Надежное клонирование с использованием библиотеки

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

  • лодаш - cloneDeep;можно импортировать отдельно через lodash.clonedeep модуль и, вероятно, это ваш лучший выбор, если вы еще не используете библиотеку, обеспечивающую функцию глубокого клонирования.
  • Угловой - angular.copy
  • jQuery — jQuery.extend(true, { }, oldObject); .clone() клонирует только элементы DOM

ES6

Для полноты отметим, что ES6 предлагает два механизма мелкого копирования: Object.assign() и оператор распространения.

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

Посмотрите этот тест: http://jsben.ch/#/bWfk9

В моих предыдущих тестах, где скорость была главной проблемой, я обнаружил

JSON.parse(JSON.stringify(obj))

быть самым медленным способом глубокого клонирования объекта (он медленнее, чем jQuery.extend с deep флаг установлен верно на 10-20%).

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

Если вы знаете структуру объектов, которые пытаетесь клонировать, или можете избежать глубоких вложенных массивов, вы можете написать простой for (var i in obj) цикл для клонирования вашего объекта при проверке hasOwnProperty, и это будет намного быстрее, чем jQuery.

Наконец, если вы пытаетесь клонировать известную структуру объекта в «горячем» цикле, вы можете получить НАМНОГО БОЛЬШЕ ПРОИЗВОДИТЕЛЬНОСТИ, просто встроив процедуру клонирования и создав объект вручную.

Механизмы трассировки JavaScript плохо справляются с оптимизацией for..in циклы и проверка hasOwnProperty также замедлят работу.Ручное клонирование, когда скорость абсолютно необходима.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

Остерегайтесь использования JSON.parse(JSON.stringify(obj)) метод на Date объекты - JSON.stringify(new Date()) возвращает строковое представление даты в формате ISO, которое JSON.parse() не делает конвертировать обратно в Date объект. Смотрите этот ответ для более подробной информации.

Кроме того, обратите внимание, что, по крайней мере, в Chrome 65 собственное клонирование не подходит.В соответствии с это JSPerf, выполнение собственного клонирования путем создания новой функции почти 800x медленнее, чем использование JSON.stringify, который невероятно быстр во всех отношениях.

Обновление для ES6

Если вы используете Javascript ES6, попробуйте этот собственный метод клонирования или поверхностного копирования.

Object.assign({}, obj);

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

var newObject = JSON.parse(JSON.stringify(oldObject));

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

Стандарт HTML включает в себя внутренний структурированный алгоритм клонирования/сериализации который может создавать глубокие клоны объектов.Он по-прежнему ограничен определенными встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает даты, регулярные выражения, карты, наборы, большие двоичные объекты, списки файлов, изображения, разреженные массивы, типизированные массивы и, возможно, многое другое в будущем. .Он также сохраняет ссылки в клонированных данных, что позволяет поддерживать циклические и рекурсивные структуры, которые могут вызвать ошибки для 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 — отправить данные через один порт Каналы сообщений.Другой порт выдаст 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 конструктор создает структурированный клон связанных с ним данных.Он также пытается отобразить пользователю уведомление браузера, но это не удастся, если вы не запросили разрешение на уведомление.Если у вас есть разрешение для других целей, мы немедленно закроем созданное нами уведомление.

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

Если встроенного не было, вы могли бы попробовать:

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

Эффективный способ клонирования (не глубокого клонирования) объекта в одной строке кода.

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

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

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

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

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

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

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

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

Код:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Тест:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

Это то, что я использую:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

Глубокая копия по производительности:Рейтинг от лучшего к худшему

  • Переназначение "=" (только строковые массивы, числовые массивы)
  • Срез (только строковые массивы, числовые массивы)
  • Конкатенация (только строковые массивы, числовые массивы)
  • Пользовательская функция:цикл for или рекурсивная копия
  • $.extend в jQuery
  • JSON.parse (только строковые массивы, числовые массивы, массивы объектов)
  • Underscore.js's _.clone (только строковые массивы, числовые массивы)
  • _.cloneDeep от Lo-Dash

Глубокое копирование массива строк или чисел (один уровень — без указателей ссылок):

Когда массив содержит числа и строки — такие функции, как .slice(), .concat(), .splice(), оператор присваивания «=" и функция клонирования Underscore.js;создаст глубокую копию элементов массива.

Где переназначение имеет наибольшую производительность:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

И .slice() имеет лучшую производительность, чем .concat(),http://jsperf.com/duulate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Глубокое копирование массива объектов (два и более уровней — указатели ссылок):

var arr1 = [{object:'a'}, {object:'b'}];

Напишите пользовательскую функцию (имеет более высокую производительность, чем $.extend() или JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Используйте сторонние утилиты:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

Где $.extend в jQuery имеет лучшую производительность:

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

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});

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

Ты можешь найди это в npm, слишком.Его можно использовать как для браузера, так и для Node.js.

Вот пример того, как его использовать:

Установите его с помощью

npm install clone

или упакуйте его с помощью Эндер.

ender build clone [...]

Вы также можете загрузить исходный код вручную.

Затем вы можете использовать его в своем исходном коде.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(Отказ от ответственности:Я автор библиотеки.)

Я знаю, что это старый пост, но я подумал, что он может помочь следующему человеку, который наткнется на него.

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

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

Cloning Объект всегда был проблемой в JS, но это было до ES6. Ниже я перечисляю различные способы копирования объекта в JavaScript. Представьте, что у вас есть объект ниже и вы хотели бы иметь его глубокую копию:

var obj = {a:1, b:2, c:3, d:4};

Есть несколько способов скопировать этот объект без изменения источника:

1) ES5+. Использование простой функции для копирования:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    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 this object.");
}

2) ES5+ с использованием JSON.parse и JSON.stringify.

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJ:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs и Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

Надеюсь, это поможет...

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

var newObject = _.clone(oldObject);

Глубокое копирование объектов в JavaScript (думаю, самое лучшее и самое простое)

1.Использование JSON.parse(JSON.stringify(object));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2.Использование созданного метода

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3.Использование _.cloneDeep от Lo-Dash связь Лодаш

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4.Использование метода Object.assign()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

НО НЕПРАВИЛЬНО, КОГДА

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Использование Underscore.js_.clone связь Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

НО НЕПРАВИЛЬНО, КОГДА

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

Ссылка на Medium.com

JSBEN.CH Площадка для сравнительного анализа производительности 1–3 http://jsben.ch/KVQLd Performance Deep copying objects in JavaScript

Вот версия ответа ConroyP выше, которая работает, даже если у конструктора есть обязательные параметры:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Эта функция также доступна в моем простооо библиотека.

Редактировать:

Вот более надежная версия (спасибо Джастину МакКэндлессу теперь также поддерживаются циклические ссылки):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

Следующее создает два экземпляра одного и того же объекта.Я нашел его и использую в настоящее время.Он прост и удобен в использовании.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

У Лодаша хороший _.cloneDeep(значение) метод:

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

Крокфорд предлагает (и я предпочитаю) использовать эту функцию:

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

var newObject = object(oldObject);

Он краток, работает так, как и ожидалось, и вам не нужна библиотека.


РЕДАКТИРОВАТЬ:

Это полифилл для Object.create, так что вы также можете использовать это.

var newObject = Object.create(oldObject);

ПРИМЕЧАНИЕ: Если вы используете что-то из этого, у вас могут возникнуть проблемы с некоторыми итерациями, которые используют hasOwnProperty.Потому что, create создать новый пустой объект, который наследует oldObject.Но это по-прежнему полезно и практично для клонирования объектов.

Например, если oldObject.a = 5;

newObject.a; // is 5

но:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

Мелкая однострочная копия (ECMAScript 5-е издание):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

И неглубокий однострочный текст (ECMAScript 6-е издание, 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

Просто потому, что я не видел AngularJS упомянул и подумал, что люди, возможно, захотят знать...

angular.copy также предоставляет метод глубокого копирования объектов и массивов.

Кажется, пока не существует идеального оператора глубокого клонирования для объектов типа массива.Как показано в приведенном ниже коде, клонер jQuery Джона Резига преобразует массивы с нечисловыми свойствами в объекты, которые не являются массивами, а клонер JSON компании RegDwight удаляет нечисловые свойства.Следующие тесты иллюстрируют эти моменты в нескольких браузерах:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

У меня есть два хороших ответа в зависимости от того, является ли ваша цель клонировать «простой старый объект JavaScript» или нет.

Предположим также, что вы намерены создать полный клон без ссылок на прототип исходного объекта.Если вас не интересует полный клон, вы можете использовать многие процедуры Object.clone(), представленные в некоторых других ответах (шаблон Крокфорда).

Для простых старых объектов JavaScript проверенный и надежный способ клонировать объект в современной среде выполнения довольно прост:

var clone = JSON.parse(JSON.stringify(obj));

Обратите внимание, что исходный объект должен быть чистым объектом JSON.То есть все его вложенные свойства должны быть скалярами (например, логическими значениями, строками, массивами, объектами и т. д.).Любые функции или специальные объекты, такие как RegExp или Date, не будут клонированы.

Эффективно ли это?Черт возьми, да.Мы испробовали все виды методов клонирования, и этот работает лучше всего.Я уверен, что какой-нибудь ниндзя сможет придумать более быстрый метод.Но я подозреваю, что мы говорим о незначительной выгоде.

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

Что касается непростых объектов JavaScript, простого ответа не существует.На самом деле этого не может быть из-за динамической природы функций JavaScript и внутреннего состояния объекта.Глубокое клонирование структуры JSON с функциями внутри требует воссоздания этих функций и их внутреннего контекста.А в JavaScript просто нет стандартизированного способа сделать это.

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

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

http://davidwalsh.name/javascript-clone

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

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

Этот код не только краток, но и очень удобен для чтения.Продлить довольно легко.

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

Итак, вот и все.Два подхода.Оба варианта, на мой взгляд, эффективны.

Обычно это не самое эффективное решение, но оно делает то, что мне нужно.Простые тестовые примеры ниже...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

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

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Проверка циклического массива...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Функциональный тест...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

AngularJS

Ну, если вы используете angular, вы тоже можете это сделать

var newObject = angular.copy(oldObject);

Я не согласен с ответом, набравшим наибольшее количество голосов здесьРекурсивное глубокое клонирование является намного быстрее чем JSON.parse(JSON.stringify(obj)) упомянутый подход.

А вот функция для быстрого ознакомления:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

Для людей, которые хотят использовать JSON.parse(JSON.stringify(obj)) версию, но без потери объектов Date вы можете использовать второй аргумент parse метод чтобы преобразовать строки обратно в дату:

function clone(obj) {
  var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
  return JSON.parse(JSON.stringify(x), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v);
    return v;
  });
}

Вот комплексный метод clone(), который может клонировать любой объект JavaScript.Он обрабатывает почти все случаи:

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;
};
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top