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

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

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

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

Я удивлен, что канонического решения не существует.

4570
задан jschrab 23 сент. '08 в 19:26
источник поделиться

64 ответов

  • 1
  • 2
  • 3

Примечание: Это ответ на другой ответ, а не правильный ответ на этот вопрос. Если вы хотите быстро клонировать объекты, пожалуйста, следуйте совету Corban в своем ответе на этот вопрос.


Хочу отметить, что метод .clone() в jQuery только клонирует элементы DOM. Чтобы клонировать объекты JavaScript, вы должны:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Более подробную информацию можно найти в документации jQuery.

Я также хочу отметить, что глубокая копия на самом деле намного умнее, чем показано выше - она ​​способна избежать многих ловушек (например, для глубокого расширения элемента DOM). Он часто используется в ядре jQuery и в плагинах с большим эффектом.

4080
ответ дан John Resig 23 сент. '08 в 21:09
источник поделиться

Ознакомьтесь с этим эталоном: http://jsben.ch/#/bWfk9

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

<Предварительно > <код > JSON.parse(JSON.stringify(OBJ)) Код >

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

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

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

Наконец, если вы пытаетесь клонировать известную структуру объекта в горячем цикле, вы можете получить MUCH MORE MORE PERFORMANCE, просто вставив процедуру клонирования и вручную создав объект.

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

  var clonedObject = { knownProp: obj.knownProp,..
}
Код>

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

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

1891
ответ дан Corban Brook 17 марта '11 в 22:19
источник поделиться

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

var newObject = JSON.parse(JSON.stringify(oldObject));
404
ответ дан Sultan Shakir 04 янв. '11 в 11:05
источник поделиться

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

HTML5 определяет внутренний "структурированный" алгоритм клонирования, который может создавать глубокие клоны объектов. Он по-прежнему ограничен определенными встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает Dates, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, редкие массивы, Typed Arrays и, вероятно, больше в будущем. Он также сохраняет ссылки в клонированных данных, что позволяет поддерживать циклические и рекурсивные структуры, которые могут вызывать ошибки для JSON.

Прямая поддержка в браузерах: скоро? 🙂

Браузеры в настоящее время не предоставляют прямой интерфейс для алгоритма структурированного клонирования, но глобальная функция structuredClone() активно обсуждается в whatwg/html # 793 on 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 создает структурированный клон связанных с ним данных. Он также пытается отобразить уведомление браузера для пользователя, но это будет беспроблемным, если вы не запросили разрешение на уведомление. Если у вас есть разрешение для других целей, мы немедленно закроем созданное нами уведомление.

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();
276
ответ дан Jeremy 06 июня '12 в 17:59
источник поделиться

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

    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;
    }
276
ответ дан ConroyP 23 сент. '08 в 19:38
источник поделиться

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

Метод 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;
    }
  });
}
135
ответ дан Eugene Tiurin 15 дек. '15 в 10:26
источник поделиться

код:

// 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);
91
ответ дан Kamarey 25 июня '09 в 10:53
источник поделиться

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

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;
}
81
ответ дан Alan 12 дек. '09 в 1:47
источник поделиться

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

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

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

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

Если переназначение имеет самую высокую производительность:

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

И.slice() имеет лучшую производительность, чем.concat(), http://jsperf.com/duplicate-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

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

64
ответ дан tfmontague 18 сент. '14 в 23:10
источник поделиться
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});
56
ответ дан Zibri 26 дек. '09 в 17:59
источник поделиться

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

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

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
49
ответ дан Joe 24 сент. '11 в 22:28
источник поделиться

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

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

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

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

npm install clone

или упакуйте его с помощью Ender.

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

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

47
ответ дан pvorb 17 окт. '12 в 21:36
источник поделиться

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

var newObject = _.clone(oldObject);
45
ответ дан itsadok 15 дек. '11 в 18:56
источник поделиться

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) AngularJs:

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

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

44
ответ дан Alireza 03 апр. '17 в 18:37
источник поделиться

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

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that 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;
}

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

Edit:

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

/**
 * 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 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();
    };
}
33
ответ дан Matt Browne 11 нояб. '12 в 20:53
источник поделиться

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

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
30
ответ дан nathan rogers 21 авг. '15 в 18:51
источник поделиться
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
22
ответ дан Mark Cidade 23 сент. '08 в 19:45
источник поделиться

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

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

var newObject = object(oldObject);

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


EDIT:

Это polyfill для 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
20
ответ дан Chris Broski 06 окт. '10 в 18:08
источник поделиться

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

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
19
ответ дан opensas 22 июня '13 в 18:03
источник поделиться

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

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
18
ответ дан Maël Nison 05 июля '12 в 0:44
источник поделиться

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

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

15
ответ дан Dan Atkinson 14 мая '16 в 1:16
источник поделиться

В Prototype вы сделаете что-то вроде

newObject = Object.clone(myObject);

Прототипная документация отмечает, что это делает мелкую копию.

14
ответ дан erlando 02 дек. '17 в 20:47
поделиться

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

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)
14
ответ дан Page Notes 17 окт. '10 в 7:01
источник поделиться

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

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

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

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

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

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

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

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

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

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

http://davidwalsh.name/javascript-clone

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

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

Это не только код, но и очень читаемый. Это довольно легко расширить.

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

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

13
ответ дан Michael Uzquiano 06 мая '13 в 23:45
источник поделиться

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

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj 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
11
ответ дан neatonk 03 апр. '11 в 5:08
источник поделиться

AngularJS

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

var newObject = angular.copy(oldObject);
9
ответ дан azerafati 14 сент. '16 в 16:26
источник поделиться
// 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;
};
9
ответ дан Dima 28 апр. '10 в 14:16
источник поделиться

Я не согласен с ответом с наибольшим количеством голосов здесь. Рекурсивный глубокий клон быстрее , чем упомянутый подход 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
}
9
ответ дан prograhammer 18 июня '17 в 9:34
источник поделиться

Вот комплексный метод 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;
};
7
ответ дан user1547016 24 июля '12 в 0:39
источник поделиться

Я обычно использую var newObj = JSON.parse( JSON.stringify(oldObje) );, но здесь более подходящий способ:

var o = {};

var oo = Object.create(o);

(o === oo); // => false

Наблюдайте за старыми браузерами!

6
ответ дан Cody 24 апр. '14 в 21:35
источник поделиться
  • 1
  • 2
  • 3

Другие вопросы по меткам