Каков наиболее эффективный способ клонирования объекта JavaScript? Я видел obj = eval(uneval(o));
, но который нестандартен и поддерживается только Firefox.
Я ' вы делали такие вещи, как obj = JSON.parse(JSON.stringify(o));
, но сомневаетесь в эффективности.
Я также видел рекурсивные функции копирования с различными недостатками.
Я удивлен, что канонического решения не существует.
Примечание: Это ответ на другой ответ, а не правильный ответ на этот вопрос. Если вы хотите быстро клонировать объекты, пожалуйста, следуйте совету Corban в своем ответе на этот вопрос.
Хочу отметить, что метод .clone()
в jQuery только клонирует элементы DOM. Чтобы клонировать объекты JavaScript, вы должны:
// Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
Более подробную информацию можно найти в документации jQuery.
Я также хочу отметить, что глубокая копия на самом деле намного умнее, чем показано выше - она способна избежать многих ловушек (например, для глубокого расширения элемента DOM). Он часто используется в ядре jQuery и в плагинах с большим эффектом.
Ознакомьтесь с этим эталоном: 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, которое невероятно быстро проходит через всю доску.
Предполагая, что у вас есть только переменные, а не какие-либо функции в вашем объекте, вы можете просто использовать:
var newObject = JSON.parse(JSON.stringify(oldObject));
Стандарт HTML включает внутренний структурированный алгоритм клонирования/сериализации, который может создавать глубокие клоны объектов. Он по-прежнему ограничен определенными встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает Dates, RegExps, Карты, Наборы, BLOB-объекты, FileLists, ImageDatas, разреженные массивы, Typed Arrays и, возможно, больше в будущем., Он также сохраняет ссылки в клонированных данных, что позволяет поддерживать циклические и рекурсивные структуры, которые могут вызвать ошибки для JSON.
Модуль v8
в Node.js в настоящее время (по состоянию на Node 11) напрямую предоставляет API структурированной сериализации, но эта функциональность по-прежнему помечена как "экспериментальная" и может быть изменена или удалена в будущих версиях. Если вы используете совместимую версию, клонирование объекта так же просто, как:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
Браузеры в настоящее время не предоставляют прямой интерфейс для алгоритма структурированного клонирования, но глобальная функция structdClone 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
создает структурированный клон связанных с ним данных. Он также пытается отобразить уведомление в браузере для пользователя, но это молча завершится ошибкой, если вы не запросили разрешение на уведомление. Если у вас есть разрешение на другие цели, мы немедленно закроем созданное нами уведомление.
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();
history.pushState()
и history.replaceState()
синхронно устанавливают history.state
для структурированного клона своего первого аргумента. Немного странно, но это работает. Я обновляю свой ответ сейчас.
Если бы не было встроенного, вы могли бы попробовать:
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() используется для копирования значений всех перечислимых собственных свойств из одного или нескольких исходных объектов в целевой объект.
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;
}
});
}
код:
// 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);
var obj = {}
и obj.a = obj
from.constructor
это Date
. Как бы третий , if
тест будет достигнут , когда второй , if
тест будет успешным и вызвать функцию для возврата (с Date != Object && Date != Array
)?
Это то, что я использую:
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;
}
cloneObject({ name: null })
=> {"name":{}}
typeof null > "object"
но Object.keys(null) > TypeError: Requested keys of a value that is not an object.
измените условие на if(typeof(obj[i])=="object" && obj[i]!=null)
Глубокая копия по производительности: от лучших до худших
Глубоко скопируйте массив строк или чисел (один уровень - без указателей):
Когда массив содержит числа и строки - такие функции, как.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 имеет лучшую производительность:
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});
Я знаю, что это старый пост, но я подумал, что это может помочь кому-то, кто спотыкается.
Пока вы не присваиваете объект чему-либо, он не поддерживает ссылку в памяти. Таким образом, чтобы создать объект, который вы хотите разделить между другими объектами, вам нужно создать factory так:
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) 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
Надеюсь, что эта помощь...
Если вы используете его, библиотека Underscore.js имеет clone.
var newObject = _.clone(oldObject);
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 автор библиотеки.)
JSON.parse(JSON.stringify(obj))
?
Здесь приведенная выше версия 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();
};
}
Следующее создает два экземпляра одного и того же объекта. Я нашел его и использую его в настоящее время. Это простой и простой в использовании.
var objToCreate = JSON.parse(JSON.stringify(cloneThis));
Глубокое копирование объектов в 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. Использование Lo-Dash _.cloneDeep link lodash
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 link 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.)
JSBEN.CH Производительность Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd
Object.assign()
не выполняет глубокое копирование
Lodash имеет хороший метод _. cloneDeep (значение):
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
.clone(...)
служебной библиотеки. Они есть в каждой крупной библиотеке, а повторяющиеся краткие, не детализированные ответы бесполезны для большинства посетителей, которые не будут использовать эту конкретную библиотеку.
_.merge({}, objA)
. Если бы только lodash не мутировал объекты в первую очередь, тогда функция clone
не была бы необходима.
Крокфорд предлагает (и я предпочитаю) использовать эту функцию:
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
function clone(obj)
{ var clone = {};
clone.prototype = obj.prototype;
for (property in obj) clone[property] = obj[property];
return clone;
}
Однострочная копия мелкой копии (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 6th edition, 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
Object.keys
пропускает не перечисляемые и унаследованные свойства. Кроме того, он теряет дескрипторы свойств, выполняя прямое присваивание.
Просто потому, что я не видел AngularJS и думал, что люди захотят узнать...
angular.copy
также предоставляет метод глубокого копирования объектов и массивов.
angular.extend({},obj);
jQuery.extend
и angular.extend
являются мелкими копиями. angular.copy
- это глубокая копия.
Кажется, что нет идеального оператора глубокого клонирования для объектов типа массива. Как видно из приведенного ниже кода, клонирующий 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)
JSON.stringify
не работает с functions
У меня есть два хороших ответа в зависимости от того, должна ли ваша цель клонировать "простой старый объект 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.
Это не только код, но и очень читаемый. Это довольно легко расширить.
Является ли это эффективным? Черт возьми. Учитывая, что цель состоит в том, чтобы создать настоящий клон с глубокой копией, вам придется ходить по членам графа исходного объекта. При таком подходе вы можете точно настроить, какие дочерние элементы обрабатывать и как вручную обрабатывать настраиваемые типы.
Итак, вы идете. Два подхода. На мой взгляд, обе эффективны.
В Prototype вы сделаете что-то вроде
newObject = Object.clone(myObject);
Прототипная документация отмечает, что это делает мелкую копию.
Это не самое эффективное решение, но оно делает то, что мне нужно. Простые тестовые примеры ниже...
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
Хорошо, если вы используете 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
}
if(o instanceof Date) return new Date(o.valueOf());
добавить что-то вроде if(o instanceof Date) return new Date(o.valueOf());
после проверки на ноль
// 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;
};
Вот комплексный метод 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;
};
Я использую библиотеку клонов npm. По-видимому, он также работает в браузере.
https://www.npmjs.com/package/clone
let a = clone(b)
Клонирование объекта с использованием JavaScript сегодня: ECMAScript 2015 (ранее известный как ECMAScript 6)
var original = {a: 1};
// Method 1: New object with original assigned.
var copy1 = Object.assign({}, original);
// Method 2: New object with spread operator assignment.
var copy2 = {...original};
В старых браузерах может не поддерживаться ECMAScript 2015. Общим решением является использование компилятора JavaScript-к-JavaScript, такого как Babel, для вывода ECMAScript 5 версия вашего кода JavaScript.
Как отмеченный @jim-hall, это только мелкая копия. Свойства свойств копируются как ссылка: изменение одного изменит значение в другом объекте/экземпляре.
Только если вы можете использовать ECMAScript 6 или transpilers.
Особенности:
код:
function clone(target, source){
for(let key in source){
// Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
let descriptor = Object.getOwnPropertyDescriptor(source, key);
if(descriptor.value instanceof String){
target[key] = new String(descriptor.value);
}
else if(descriptor.value instanceof Array){
target[key] = clone([], descriptor.value);
}
else if(descriptor.value instanceof Object){
let prototype = Reflect.getPrototypeOf(descriptor.value);
let cloneObject = clone({}, descriptor.value);
Reflect.setPrototypeOf(cloneObject, prototype);
target[key] = cloneObject;
}
else {
Object.defineProperty(target, key, descriptor);
}
}
let prototype = Reflect.getPrototypeOf(source);
Reflect.setPrototypeOf(target, prototype);
return target;
}
Я обычно использую var newObj = JSON.parse( JSON.stringify(oldObje) );
, но здесь более подходящий способ:
var o = {};
var oo = Object.create(o);
(o === oo); // => false
Наблюдайте за старыми браузерами!
Я опаздываю, чтобы ответить на этот вопрос, но у меня есть другой способ клонирования объекта:
function cloneObject(obj) {
if (obj === null || typeof(obj) !== 'object')
return obj;
var temp = obj.constructor(); // changed
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = cloneObject(obj[key]);
delete obj['isActiveClone'];
}
}
return temp;
}
var b = cloneObject({"a":1,"b":2}); // calling
который намного лучше и быстрее, чем:
var a = {"a":1,"b":2};
var b = JSON.parse(JSON.stringify(a));
и
var a = {"a":1,"b":2};
// Deep copy
var newObject = jQuery.extend(true, {}, a);
У меня есть ярлык кода, и вы можете проверить результаты здесь:
и поделиться результатами: Ссылки: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
У Lodash есть функция, которая обрабатывает это для вас.
var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};
var bar = _.cloneDeep(foo);
// bar = {a: 'a', b: {c:'d', e: {f: 'g'}}}
Прочитайте документы здесь.
//Deep copy: _.merge({},foo) //Shallow copy: Object.Assign({}, foo)
Однострочное решение ECMAScript 6 (специальные типы объектов, такие как Date/Regex, не обрабатываются):
const clone = (o) =>
typeof o === 'object' && o !== null ? // only clone objects
(Array.isArray(o) ? // if cloning an array
o.map(e => clone(e)) : // clone each of its elements
Object.keys(o).reduce( // otherwise reduce every key in the object
(r, k) => (r[k] = clone(o[k]), r), {} // and save its cloned value into a new object
)
) :
o; // return non-objects as is
var x = {
nested: {
name: 'test'
}
};
var y = clone(x);
console.log(x.nested !== y.nested);
Пример ES 2017:
let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
{ foo: 1, bar: { fooBar: 22, fooBaz: 33, fooFoo: 11 }, baz: 3}
и { foo: 1, bar: { fooBar: 22, fooBaz: 44, fooFoo: 11 }, baz: 4}
. Разве это не то, что вы ожидаете?
Для справки в будущем, текущий проект ECMAScript 6 представляет Object.assign как способ клонирования объектов. Пример кода:
var obj1 = { a: true, b: 1 };
var obj2 = Object.assign(obj1);
console.log(obj2); // { a: true, b: 1 }
На момент написания поддержка ограничивается Firefox 34 в браузерах, поэтому ее нельзя использовать в производственном коде только пока (если вы не написали Firefox расширение курса).
obj2 = Object.assign({}, obj1)
. Ваш текущий код эквивалентен obj2 = obj1
.
const o1 = { a: { deep: 123 } }; const o2 = Object.assign({}, o1); o2.a.deep = 456;
теперь o1.a.deep === 456
тоже.
Это самый быстрый метод, который я создал, который не использует прототип, поэтому он будет поддерживать hasOwnProperty в новом объекте.
Решением является итерация свойств верхнего уровня исходного объекта, создание двух копий, удаление каждого свойства из оригинала, а затем reset исходного объекта и возврат новой копии. Он должен только перебирать столько раз, сколько свойства верхнего уровня. Это сохраняет все условия if
, чтобы проверить, является ли каждое свойство функцией, объектом, строкой и т.д., И не требует итерации каждого свойства потомка.
Единственным недостатком является то, что исходный объект должен быть снабжен его исходным созданным пространством имен, чтобы reset он.
copyDeleteAndReset:function(namespace,strObjName){
var obj = namespace[strObjName],
objNew = {},objOrig = {};
for(i in obj){
if(obj.hasOwnProperty(i)){
objNew[i] = objOrig[i] = obj[i];
delete obj[i];
}
}
namespace[strObjName] = objOrig;
return objNew;
}
var namespace = {};
namespace.objOrig = {
'0':{
innerObj:{a:0,b:1,c:2}
}
}
var objNew = copyDeleteAndReset(namespace,'objOrig');
objNew['0'] = 'NEW VALUE';
console.log(objNew['0']) === 'NEW VALUE';
console.log(namespace.objOrig['0']) === innerObj:{a:0,b:1,c:2};
Как насчет асинхронного клонирования объектов, выполненного с помощью Promise
?
async function clone(thingy /**/)
{
if(thingy instanceof Promise)
{
throw Error("This function cannot clone Promises.");
}
return thingy;
}
Вот мой способ глубокого клонирования объекта с ES2015
значением по умолчанию и оператором распространения
const makeDeepCopy = (obj, copy = {}) => {
for (let item in obj) {
if (typeof obj[item] === 'object') {
makeDeepCopy(obj[item], copy)
}
if (obj.hasOwnProperty(item)) {
copy = {
...obj
}
}
}
return copy
}
const testObj = {
"type": "object",
"properties": {
"userId": {
"type": "string",
"chance": "guid"
},
"emailAddr": {
"type": "string",
"chance": {
"email": {
"domain": "fake.com"
}
},
"pattern": "[email protected]"
}
},
"required": [
"userId",
"emailAddr"
]
}
const makeDeepCopy = (obj, copy = {}) => {
for (let item in obj) {
if (typeof obj[item] === 'object') {
makeDeepCopy(obj[item], copy)
}
if (obj.hasOwnProperty(item)) {
copy = {
...obj
}
}
}
return copy
}
console.log(makeDeepCopy(testObj))
Существует так много способов добиться этого, но если вы хотите сделать это без какой-либо библиотеки, вы можете использовать следующее:
const cloneObject = (oldObject) => {
let newObject = oldObject;
if (oldObject && typeof oldObject === 'object') {
if(Array.isArray(oldObject)) {
newObject = [];
} else if (Object.prototype.toString.call(oldObject) === '[object Date]' && !isNaN(oldObject)) {
newObject = new Date(oldObject.getTime());
} else {
newObject = {};
for (let i in oldObject) {
newObject[i] = cloneObject(oldObject[i]);
}
}
}
return newObject;
}
Сообщите мне, что вы думаете.
Это моя версия клонирования объектов. Это автономная версия метода jQuery с небольшими настройками и настройками. Проверьте fiddle. Я использовал много jQuery до того дня, когда понял, что большую часть времени я использую только эту функцию x_x.
Использование такое же, как описано в jQuery API:
extend(object_dest, object_source);
extend(true, object_dest, object_source);
Одна дополнительная функция используется для определения того, правильно ли клонирован объект.
/**
* This is a quasi clone of jQuery extend() function.
* by Romain WEEGER for wJs library - www.wexample.com
* @returns {*|{}}
*/
function extend() {
// Make a copy of arguments to avoid JavaScript inspector hints.
var to_add, name, copy_is_array, clone,
// The target object who receive parameters
// form other objects.
target = arguments[0] || {},
// Index of first argument to mix to target.
i = 1,
// Mix target with all function arguments.
length = arguments.length,
// Define if we merge object recursively.
deep = false;
// Handle a deep copy situation.
if (typeof target === 'boolean') {
deep = target;
// Skip the boolean and the target.
target = arguments[ i ] || {};
// Use next object as first added.
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if (typeof target !== 'object' && typeof target !== 'function') {
target = {};
}
// Loop trough arguments.
for (false; i < length; i += 1) {
// Only deal with non-null/undefined values
if ((to_add = arguments[ i ]) !== null) {
// Extend the base object.
for (name in to_add) {
// We do not wrap for loop into hasOwnProperty,
// to access to all values of object.
// Prevent never-ending loop.
if (target === to_add[name]) {
continue;
}
// Recurse if we're merging plain objects or arrays.
if (deep && to_add[name] && (is_plain_object(to_add[name]) || (copy_is_array = Array.isArray(to_add[name])))) {
if (copy_is_array) {
copy_is_array = false;
clone = target[name] && Array.isArray(target[name]) ? target[name] : [];
}
else {
clone = target[name] && is_plain_object(target[name]) ? target[name] : {};
}
// Never move original objects, clone them.
target[name] = extend(deep, clone, to_add[name]);
}
// Don't bring in undefined values.
else if (to_add[name] !== undefined) {
target[name] = to_add[name];
}
}
}
}
return target;
}
/**
* Check to see if an object is a plain object
* (created using "{}" or "new Object").
* Forked from jQuery.
* @param obj
* @returns {boolean}
*/
function is_plain_object(obj) {
// Not plain objects:
// - Any object or value whose internal [[Class]] property is not "[object Object]"
// - DOM nodes
// - window
if (obj === null || typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {
return false;
}
// Support: Firefox <20
// The try/catch suppresses exceptions thrown when attempting to access
// the "constructor" property of certain host objects, i.e. |window.location|
// https://bugzilla.mozilla.org/show_bug.cgi?id=814622
try {
if (obj.constructor && !this.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
}
catch (e) {
return false;
}
// If the function hasn't returned already, we're confident that
// |obj| is a plain object, created by {} or constructed with new Object
return true;
}
|| typeof target[name] !== "undefined"
при тестировании if (target === to_add[name]) { continue; }
чтобы не перезаписывать существующие члены target
. Например, var a={hello:"world", foo:"bar"}; var b={hello:"you"}; extend(b, a);
мы ожидаем найти b => {hello:"you", foo:"bar"}
, но с вашим кодом мы найдем: b => {hello:"world", foo:"bar"}
Есть много ответов, но ни один из них не дал желаемого эффекта, в котором я нуждался. Я хотел использовать мощь глубокой копии jQuery... Однако, когда она запускается в массив, она просто копирует ссылку на массив и глубоко копирует элементы в ней. Чтобы обойти это, я сделал небольшую рекурсивную функцию, которая автоматически создаст новый массив.
(Он даже проверяет для kendo.data.ObservableArray, если вы этого хотите! Хотя, убедитесь, что вы вызываете kendo.observable(newItem), если вы хотите, чтобы массивы снова наблюдались.)
Итак, чтобы полностью скопировать существующий элемент, вы просто выполните следующее:
var newItem = jQuery.extend(true, {}, oldItem);
createNewArrays(newItem);
function createNewArrays(obj) {
for (var prop in obj) {
if ((kendo != null && obj[prop] instanceof kendo.data.ObservableArray) || obj[prop] instanceof Array) {
var copy = [];
$.each(obj[prop], function (i, item) {
var newChild = $.extend(true, {}, item);
createNewArrays(newChild);
copy.push(newChild);
});
obj[prop] = copy;
}
}
}
Я думаю, что это лучшее решение, если вы хотите обобщить алгоритм клонирования объектов.
Он может использоваться с jQuery или без него, хотя я рекомендую оставить метод расширения jQuery, если вы хотите, чтобы клонированный объект имел тот же "класс", что и исходный.
function clone(obj){
if(typeof(obj) == 'function')//it a simple function
return obj;
//of it not an object (but could be an array...even if in javascript arrays are objects)
if(typeof(obj) != 'object' || obj.constructor.toString().indexOf('Array')!=-1)
if(JSON != undefined)//if we have the JSON obj
try{
return JSON.parse(JSON.stringify(obj));
}catch(err){
return JSON.parse('"'+JSON.stringify(obj)+'"');
}
else
try{
return eval(uneval(obj));
}catch(err){
return eval('"'+uneval(obj)+'"');
}
// I used to rely on jQuery for this, but the "extend" function returns
//an object similar to the one cloned,
//but that was not an instance (instanceof) of the cloned class
/*
if(jQuery != undefined)//if we use the jQuery plugin
return jQuery.extend(true,{},obj);
else//we recursivley clone the object
*/
return (function _clone(obj){
if(obj == null || typeof(obj) != 'object')
return obj;
function temp () {};
temp.prototype = obj;
var F = new temp;
for(var key in obj)
F[key] = clone(obj[key]);
return F;
})(obj);
}
В JavaScript вы можете написать свой метод
deepCopy
как
function deepCopy(src) {
let target = Array.isArray(src) ? [] : {};
for (let prop in src) {
let value = src[prop];
if(value && typeof value === 'object') {
target[prop] = deepCopy(value);
} else {
target[prop] = value;
}
}
return target;
}
Для мелкой копии есть простой, простой метод, введенный в стандарте ECMAScript2018. Это связано с использованием оператора Spread:
let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };
let objClone = { ...obj };
Я протестировал его в браузере Chrome, оба объекта хранятся в разных местах, поэтому изменение мгновенных дочерних значений либо не изменит другой. Хотя (в примере) изменение значения в e
будет влиять на обе копии.
Этот метод очень прост и прост. Я считаю это настоящей лучшей практикой для этого вопроса раз и навсегда.
Spread in object literals
разделе Spread in object literals
По моему опыту, рекурсивная версия значительно превосходит JSON.parse(JSON.stringify(obj))
. Вот модернизированная функция рекурсивного глубокого копирования объектов, которая может вписываться в одну строку:
function deepCopy(obj) {
return Object.keys(obj).reduce((v, d) => Object.assign(v, {
[d]: (obj[d].constructor === Object) ? deepCopy(obj[d]) : obj[d]
}), {});
}
Это выполняется примерно в 40 раз быстрее, чем метод JSON.parse...
Object
(нельзя поверхностное копирование), функция рекурсивно вызывает себя со значением в качестве аргумента.
Не касаясь прототипического наследования, вы можете сделать глубокие одиночные объекты и массивы следующим образом:
function objectClone(o){
var ot = Array.isArray(o);
return o !== null && typeof o === "object" ? Object.keys(o)
.reduce((r,k) => o[k] !== null && typeof o[k] === "object" ? (r[k] = objectClone(o[k]),r)
: (r[k] = o[k],r), ot ? [] : {})
: o;
}
var obj = {a: 1, b: {c: 2, d: {e: 3, f: {g: 4, h: null}}}},
arr = [1,2,[3,4,[5,6,[7]]]],
nil = null,
clobj = objectClone(obj),
clarr = objectClone(arr),
clnil = objectClone(nil);
console.log(clobj, obj === clobj);
console.log(clarr, arr === clarr);
console.log(clnil, nil === clnil);
clarr[2][2][2] = "seven";
console.log(arr, clarr);
class Handler {
static deepCopy (obj) {
if (Object.prototype.toString.call(obj) === '[object Array]') {
const result = [];
for (let i = 0, len = obj.length; i < len; i++) {
result[i] = Handler.deepCopy(obj[i]);
}
return result;
} else if (Object.prototype.toString.call(obj) === '[object Object]') {
const result = {};
for (let prop in obj) {
result[prop] = Handler.deepCopy(obj[prop]);
}
return result;
}
return obj;
}
}
В дальнейшем для справки можно использовать этот код
ES6:
_clone: function(obj){
let newObj = {};
for(let i in obj){
if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
ES5:
function clone(obj){
let newObj = {};
for(let i in obj){
if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
newObj[i] = clone(obj[i]);
} else{
newObj[i] = obj[i];
}
}
return Object.assign({},newObj);
}
Например,
var obj ={a:{b:1,c:3},d:4,e:{f:6}}
var xc = clone(obj);
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:1,c:3},d:4,e:{f:6}}
xc.a.b = 90;
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:90,c:3},d:4,e:{f:6}}
Это решение с рекурсией:
obj = {
a: { b: { c: { d: ['1', '2'] } } },
e: 'Saeid'
}
const Clone = function (obj) {
const container = Array.isArray(obj) ? [] : {}
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if(typeof obj[key] == 'object') {
container[key] = Clone(obj[key])
}
else
container[key] = obj[key].slice()
}
return container
}
console.log(Clone(obj))
Поскольку рекурсия слишком дорогая для JavaScript, и большинство ответов, которые я нашел, используют рекурсию, в то время как подход JSON пропускает не-JSON-конвертируемые части (функция и т.д.). Поэтому я сделал небольшое исследование и нашел эту технику батута, чтобы избежать этого. Здесь код:
/*
* Trampoline to avoid recursion in JavaScript, see:
* http://www.integralist.co.uk/posts/js-recursion.html
*/
function trampoline() {
var func = arguments[0];
var args = [];
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
var currentBatch = func.apply(this, args);
var nextBatch = [];
while (currentBatch && currentBatch.length > 0) {
currentBatch.forEach(function(eachFunc) {
var ret = eachFunc();
if (ret && ret.length > 0) {
nextBatch = nextBatch.concat(ret);
}
});
currentBatch = nextBatch;
nextBatch = [];
}
};
/*
* Deep clone an object using the trampoline technique.
*
* @param target {Object} Object to clone
* @return {Object} Cloned object.
*/
function clone(target) {
if (typeof target !== 'object') {
return target;
}
if (target == null || Object.keys(target).length == 0) {
return target;
}
function _clone(b, a) {
var nextBatch = [];
for (var key in b) {
if (typeof b[key] === 'object' && b[key] !== null) {
if (b[key] instanceof Array) {
a[key] = [];
}
else {
a[key] = {};
}
nextBatch.push(_clone.bind(null, b[key], a[key]));
}
else {
a[key] = b[key];
}
}
return nextBatch;
};
var ret = target instanceof Array ? [] : {};
(trampoline.bind(null, _clone))(target, ret);
return ret;
};
Также см. эту статью: https://gist.github.com/SeanOceanHu/7594cafbfab682f790eb
Требуется новый-хэш-браузер, но...
Позвольте расширить собственный объект и получить реальный .extend()
;
Object.defineProperty(Object.prototype, 'extend', {
enumerable: false,
value: function(){
var that = this;
Array.prototype.slice.call(arguments).map(function(source){
var props = Object.getOwnPropertyNames(source),
i = 0, l = props.length,
prop;
for(; i < l; ++i){
prop = props[i];
if(that.hasOwnProperty(prop) && typeof(that[prop]) === 'object'){
that[prop] = that[prop].extend(source[prop]);
}else{
Object.defineProperty(that, prop, Object.getOwnPropertyDescriptor(source, prop));
}
}
});
return this;
}
});
Просто поместите этот код перед любым кодом, который использует .extend() для объекта.
Пример:
var obj1 = {
node1: '1',
node2: '2',
node3: 3
};
var obj2 = {
node1: '4',
node2: 5,
node3: '6'
};
var obj3 = ({}).extend(obj1, obj2);
console.log(obj3);
// Object {node1: "4", node2: 5, node3: "6"}
Используйте Object.create()
, чтобы получить prototype
и поддержку instanceof
, и используйте цикл for()
для получения перечисляемых ключей:
function cloneObject(source) {
var key,value;
var clone = Object.create(source);
for (key in source) {
if (source.hasOwnProperty(key) === true) {
value = source[key];
if (value!==null && typeof value==="object") {
clone[key] = cloneObject(value);
} else {
clone[key] = value;
}
}
}
return clone;
}
Object.create(Object.getPrototypeOf(source))
вместо наследования свойств, которые вы также перезаписываете?
если вы обнаружите, что делаете этот тип вещей обычным (eg-, создавая функциональные возможности отмены отмены), возможно, стоит посмотреть на Immutable.js
const map1 = Immutable.fromJS( { a: 1, b: 2, c: { d: 3 } } );
const map2 = map1.setIn( [ 'c', 'd' ], 50 );
console.log( '${ map1.getIn( [ 'c', 'd' ] ) } vs ${ map2.getIn( [ 'c', 'd' ] ) }' ); // "3 vs 50"
Когда ваш объект вложен и он содержит объект данных, другой структурированный объект или некоторый объект свойства и т.д., JSON.parse(JSON.stringify(object))
с помощью JSON.parse(JSON.stringify(object))
или Object.assign({}, obj)
или $.extend(true, {}, obj)
не будет работать. В этом случае используйте lodash. Это просто и легко..
var obj = {a: 25, b: {a: 1, b: 2}, c: new Date(), d: anotherNestedObject };
var A = _.cloneDeep(obj);
Теперь A будет вашим новым клонированным obj без каких-либо ссылок..
Надеюсь это поможет.
function deepClone(obj) {
/*
* Duplicates an object
*/
var ret = null;
if (obj !== Object(obj)) { // primitive types
return obj;
}
if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) { // string objecs
ret = obj; // for ex: obj = new String("Spidergap")
} else if (obj instanceof Date) { // date
ret = new obj.constructor();
} else
ret = Object.create(obj.constructor.prototype);
var prop = null;
var allProps = Object.getOwnPropertyNames(obj); //gets non enumerables also
var props = {};
for (var i in allProps) {
prop = allProps[i];
props[prop] = false;
}
for (i in obj) {
props[i] = i;
}
//now props contain both enums and non enums
var propDescriptor = null;
var newPropVal = null; // value of the property in new object
for (i in props) {
prop = obj[i];
propDescriptor = Object.getOwnPropertyDescriptor(obj, i);
if (Array.isArray(prop)) { //not backward compatible
prop = prop.slice(); // to copy the array
} else
if (prop instanceof Date == true) {
prop = new prop.constructor();
} else
if (prop instanceof Object == true) {
if (prop instanceof Function == true) { // function
if (!Function.prototype.clone) {
Function.prototype.clone = function() {
var that = this;
var temp = function tmp() {
return that.apply(this, arguments);
};
for (var ky in this) {
temp[ky] = this[ky];
}
return temp;
}
}
prop = prop.clone();
} else // normal object
{
prop = deepClone(prop);
}
}
newPropVal = {
value: prop
};
if (propDescriptor) {
/*
* If property descriptors are there, they must be copied
*/
newPropVal.enumerable = propDescriptor.enumerable;
newPropVal.writable = propDescriptor.writable;
}
if (!ret.hasOwnProperty(i)) // when String or other predefined objects
Object.defineProperty(ret, i, newPropVal); // non enumerable
}
return ret;
}
Просматривая этот длинный список ответов, почти все решения были рассмотрены, кроме тех, о которых я знаю. Это список способов VANILLA JS для глубокого клонирования объекта.
JSON.parse(JSON.stringify(obj));
Через history.state с pushState или replaceState
API веб-уведомлений, но у этого есть недостаток в том, чтобы просить пользователя разрешений.
Выполнение собственного рекурсивного цикла через объект для копирования каждого уровня.
Ответ, который я не видел, → Использование ServiceWorkers. Сообщения (объекты), передаваемые между страницей и службой ServiceWorker script, будут глубокими клонами любого объекта.
Поскольку этот вопрос имеет много внимания и ответов со ссылкой на встроенные функции, такие как Object.assign или пользовательский код для глубокого клонирования, я хотел бы поделиться некоторыми библиотеками с глубоким клоном,
1. esclone
npm install --savedev esclone https://www.npmjs.com/package/esclone
Пример использования ES6:
import esclone from "esclone";
const rockysGrandFather = {
name: "Rockys grand father",
father: "Don't know :("
};
const rockysFather = {
name: "Rockys Father",
father: rockysGrandFather
};
const rocky = {
name: "Rocky",
father: rockysFather
};
const rockyClone = esclone(rocky);
Пример использования ES5:
var esclone = require("esclone")
var foo = new String("abcd")
var fooClone = esclone.default(foo)
console.log(fooClone)
console.log(foo === fooClone)
2. глубокая копия
npm установить глубокую копию https://www.npmjs.com/package/deep-copy
Пример:
var dcopy = require('deep-copy')
// deep copy object
var copy = dcopy({a: {b: [{c: 5}]}})
// deep copy array
var copy = dcopy([1, 2, {a: {b: 5}}])
3. Клон-глубокий
$npm install --save clone-deep https://www.npmjs.com/package/clone-deep
Пример:
var cloneDeep = require('clone-deep');
var obj = {a: 'b'};
var arr = [obj];
var copy = cloneDeep(arr);
obj.c = 'd';
console.log(copy);
//=> [{a: 'b'}]
console.log(arr);
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.");
}
используйте следующий метод вместо JSON.parse(JSON.stringify(obj))
потому что он медленнее, чем следующий метод
Как насчет слияния ключей объекта с его значениями?
function deepClone(o) {
var keys = Object.keys(o);
var values = Object.values(o);
var clone = {};
keys.forEach(function(key, i) {
clone[key] = values[i];
});
return clone;
}
var clone = { ...originalObject };
Вы можете сделать это, как показано ниже:
var oldObj = [1,2,3,4,5];
var newObj = oldObj.map(x => Object.assign({}, x));
function deepCloneObj(obj){
let newObj = {},
mapLike = Object.entries(obj),
deepCloneMapLikeObj = JSON.parse(JSON.stringify(map));
for(let [key,value] of deepCloneMapLikeObj){
newObj[key] = value;
}
return newObj;
}
Now Try yourself
var obj = {a:1,b:{c:2,d:3}}; var deepClone = deepCloneObj(obj); deepClone.b.c = 9; deepClone.b.c === obj.b.c // 9 === 3
Если вы используете ES6 (или выше) или typescript, вы можете попробовать
let original_obj = { 1:2, 3:4 };
let cloned_obj;
cloned_obj = { ...original_obj };
Лучший и самый современный способ сделать этот клон выглядит следующим образом:
с помощью оператора распространения ES... Пример:
var clonedObjArray = [...oldObjArray];
таким образом мы распределяем массив на отдельные значения и помещаем его в новый массив с помощью оператора [].
вот длинный пример, показывающий разные способы его работы.
let objArray = [ {a:1} , {b:2} ];
let refArray = objArray; // this will just point to the objArray
let clonedArray = [...objArray]; // will clone the array
console.log( "before:" );
console.log( "obj array" , objArray );
console.log( "ref array" , refArray );
console.log( "cloned array" , clonedArray );
objArray[0] = {c:3};
console.log( "after:" );
console.log( "obj array" , objArray ); // [ {c:3} , {b:2} ]
console.log( "ref array" , refArray ); // [ {c:3} , {b:2} ]
console.log( "cloned array" , clonedArray ); // [ {a:1} , {b:2} ]
В моих целях самым изящным способом создания нового объекта из существующего (клона) является использование функции "назначить" объекту JavaScript.
foo = {bar: 10, baz: {quox: 'batman'}};
clonedObject = Object.assign(foo);
clonedObject
теперь является копией foo
. Я не знаю деталей с точки зрения того, насколько хорошо это работает или что такое "глубокая" копия, но я использую ее, чтобы также объединить атрибуты объекта с другими объектами. Кажется, нужно работать и на клонирование.