Как глубоко клонировать в javascript
как вы глубоко клонировать объект Javascript?
Я знаю, что существуют различные функции, основанные на фреймворках, таких как JSON.parse(JSON.stringify(o)) и $.extend(true, {}, o) но я не хочу использовать фреймворк такой.
каков самый элегантный или эффективный способ создания глубокого клона.
мы заботимся о пограничных случаях, как клонирование массива. Не нарушая цепочек прототипов, работа с собственной ссылкой.
мы не заботимся о поддержке копирования объектов DOM и подобных потому что .cloneNode существует по этой причине.
как я в основном хочу использовать глубокие клоны в node.js использование ES5 функций двигателя V8 является приемлемым.
[Edit]
прежде чем кто-либо предложит, позвольте мне упомянуть, что существует явная разница между созданием копии путем прототипического наследования от объекта и клонирование его. Первый делает беспорядок из прототипа цепи.
[Изменить]
после прочтения вашего ответа я дошло до досадного открытия, что клонирование целых объектов-это очень опасная и сложная игра. Возьмем для примера следующий объект на основе закрытия
var o = (function() {
var magic = 42;
var magicContainer = function() {
this.get = function() { return magic; };
this.set = function(i) { magic = i; };
}
return new magicContainer;
}());
var n = clone(o); // how to implement clone to support closures
есть ли способ написать функцию клонирования, которая клонирует объект, имеет то же состояние во время клонирования, но не может изменить состояние o без написания парсера JS в JS.
не должно быть никакой реальной потребности мира для такой функции больше. Это чисто академический интерес.
13 ответов:
это действительно зависит от того, что вы хотите клонировать. Это действительно объект JSON или просто любой объект в JavaScript? Если вы хотите сделать любой клон, это может привести к некоторым проблемам. Какие проблемы? Я объясню это ниже, но сначала пример кода, который клонирует объектные литералы, любые примитивы, массивы и узлы DOM.
function clone(item) { if (!item) { return item; } // null, undefined values check var types = [ Number, String, Boolean ], result; // normalizing primitives if someone did new String('aaa'), or new Number('444'); types.forEach(function(type) { if (item instanceof type) { result = type( item ); } }); if (typeof result == "undefined") { if (Object.prototype.toString.call( item ) === "[object Array]") { result = []; item.forEach(function(child, index, array) { result[index] = clone( child ); }); } else if (typeof item == "object") { // testing that this is DOM if (item.nodeType && typeof item.cloneNode == "function") { result = item.cloneNode( true ); } else if (!item.prototype) { // check that this is a literal if (item instanceof Date) { result = new Date(item); } else { // it is an object literal result = {}; for (var i in item) { result[i] = clone( item[i] ); } } } else { // depending what you would like here, // just keep the reference, or create new object if (false && item.constructor) { // would not advice to do that, reason? Read below result = new item.constructor(); } else { result = item; } } } else { result = item; } } return result; } var copy = clone({ one : { 'one-one' : new String("hello"), 'one-two' : [ "one", "two", true, "four" ] }, two : document.createElement("div"), three : [ { name : "three-one", number : new Number("100"), obj : new function() { this.name = "Object test"; } } ] })а теперь поговорим о проблемах, которые могут возникнуть при клонировании реальных объектов. Я говорю сейчас об объектах, которые вы создаете, делая что-то вроде
var User = function(){} var newuser = new User();конечно, вы можете клонировать их, это не проблема, каждый объект предоставляет свойство конструктора, и вы можете использовать его для клонирования объектов, но это не всегда будет работать. Вы также можете сделать простой
for inна эти объекты, но он идет в том же направлении - беда. Я также включил функцию клонирования внутри кода, но она исключенаif( false )заявление.Итак, почему клонирование может быть боль? Ну, во-первых, каждый объект/экземпляр может иметь некоторое состояние. Вы никогда не можете быть уверены, что ваши объекты не имеют, например, частных переменных, и если это так, клонируя объект, вы просто нарушаете состояние.
представьте, что нет никакого государства, это нормально. Тогда у нас еще одна проблема. Клонирование с помощью метода "конструктор" даст нам еще одно препятствие. Это зависимость от аргументов. Вы никогда не можете быть уверены, что кто-то, кто создал этот объект, не сделал, какой-то
new User({ bike : someBikeInstance });если это так, вы не повезло, someBikeInstance, вероятно, был создан в каком-то контексте, и этот контекст неизвестен для метода clone.
так что делать? Вы все еще можете сделать
for inрешение, и относиться к таким объектам, как обычные объектные литералы, но, может быть, это идея не клонировать такие объекты вообще, а просто передать ссылку на этот объект?другое решение - вы можете установить соглашение о том, что все объекты, которые должны быть клонированы, должны реализовать эту часть сами по себе и предоставить соответствующий метод API (например, cloneObject ). Что-то какое
cloneNodeделает для дома.решать Вам.
очень простой способ, может быть, слишком просто:
var cloned = JSON.parse(JSON.stringify(objectToClone));
The
JSON.parse(JSON.stringify())комбинация для глубокого копирования объектов Javascript является неэффективным взломом, потому что JSON не поддерживает значенияundefinedиfunction () {}, и поэтомуJSON.stringifyбудет игнорировать эти разделы кода, когда "stringifying" (маршалинг) объект Javascript в JSON.следующая функция будет глубоко копировать объекты и не требует сторонней библиотеки (jQuery, LoDash и т. д.).
function copy(aObject) { if (!aObject) { return aObject; } var bObject, v, k; bObject = Array.isArray(aObject) ? [] : {}; for (k in aObject) { v = aObject[k]; bObject[k] = (typeof v === "object") ? copy(v) : v; } return bObject; }
The подчеркивания.JS-библиотека ВНО библиотека имеет функцию под названием снимок что глубоко клонирует объект
фрагмент из источника:
snapshot: function(obj) { if(obj == null || typeof(obj) != 'object') { return obj; } var temp = new obj.constructor(); for(var key in obj) { if (obj.hasOwnProperty(key)) { temp[key] = _.snapshot(obj[key]); } } return temp; }Как только библиотека будет связана с вашим проектом, вызовите функцию просто с помощью
_.snapshot(object);
вот функция ES6, которая также будет работать для объектов с циклическими ссылками:
function deepClone(obj, hash = new WeakMap()) { if (Object(obj) !== obj) return obj; // primitives if (hash.has(obj)) return hash.get(obj); // cyclic reference const result = obj instanceof Date ? new Date(obj) : obj instanceof RegExp ? new RegExp(obj.source, obj.flags) : obj.constructor ? new obj.constructor() : Object.create(null); hash.set(obj, result); if (obj instanceof Map) Array.from(obj, ([key, val]) => result.set(key, deepClone(val, hash)) ); return Object.assign(result, ...Object.keys(obj).map ( key => ({ [key]: deepClone(obj[key], hash) }) )); } // Sample data var p = { data: 1, children: [{ data: 2, parent: null }] }; p.children[0].parent = p; var q = deepClone(p); console.log(q.children[0].parent.data); // 1
Это метод глубокого клонирования, который я использую, я думаю, что это Отлично, надеюсь, вы сделаете предложения
function deepClone (obj) { var _out = new obj.constructor; var getType = function (n) { return Object.prototype.toString.call(n).slice(8, -1); } for (var _key in obj) { if (obj.hasOwnProperty(_key)) { _out[_key] = getType(obj[_key]) === 'Object' || getType(obj[_key]) === 'Array' ? deepClone(obj[_key]) : obj[_key]; } } return _out; }
как отмечали другие по этому и подобным вопросам, клонирование "объекта", в общем смысле, сомнительно в JavaScript.
однако, есть класс объектов, которые я называю" данные " объекты, то есть те, которые построены просто из
{ ... }литералы и / или простые назначения свойств или десериализованные из JSON, для которых разумно клонировать. Только сегодня я хотел искусственно раздуть данные, полученные с сервера в 5 раз, чтобы проверить, что происходит для большого набора данных, но объект (массив) и его дочерние элементы должны были быть различными объектами, чтобы вещи функционировали правильно. Клонирование позволило мне сделать это, чтобы умножить мои данные:return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));другое место, где я часто клонирую объекты данных, - это отправка данных обратно на Хост, где я хочу удалить поля состояния из объекта в модели данных перед его отправкой. Например, я могу захотеть удалить все поля, начинающиеся с"_", из объекта по мере его клонирования.
это код, который я в конечном итоге писать, чтобы сделать это в общем виде, включая поддержку массивов и селектор, чтобы выбрать, какие члены клонировать (который использует строку "путь" для определения контекста):
function clone(obj,sel) { return (obj ? _clone("",obj,sel) : obj); } function _clone(pth,src,sel) { var ret=(src instanceof Array ? [] : {}); for(var key in src) { if(!src.hasOwnProperty(key)) { continue; } var val=src[key], sub; if(sel) { sub+=pth+"/"+key; if(!sel(sub,key,val)) { continue; } } if(val && typeof(val)=='object') { if (val instanceof Boolean) { val=Boolean(val); } else if(val instanceof Number ) { val=Number (val); } else if(val instanceof String ) { val=String (val); } else { val=_clone(sub,val,sel); } } ret[key]=val; } return ret; }самое простое разумное решение глубокого клонирования, предполагающее ненулевой корневой объект и без выбора члена:
function clone(src) { var ret=(src instanceof Array ? [] : {}); for(var key in src) { if(!src.hasOwnProperty(key)) { continue; } var val=src[key]; if(val && typeof(val)=='object') { val=clone(val); } ret[key]=val; } return ret; }
Lo-Dash, теперь надмножество подчеркивания.js, имеет несколько глубоких функций клонирования:
_.cloneDeep(object)
_.cloneDeepWith(object, (val) => {if(_.isElement(val)) return val.cloneNode(true)})второй параметр-это функция, которая вызывается для получения клонированных значение.
lodash underscoreсборка предоставляется для обеспечения совместимости с последней стабильной версией подчеркивания.
Я заметил, что карта должна требовать специального обращения, поэтому со всеми предложениями в этой теме код будет:
function deepClone( obj ) { if( !obj || true == obj ) //this also handles boolean as true and false return obj; var objType = typeof( obj ); if( "number" == objType || "string" == objType ) // add your immutables here return obj; var result = Array.isArray( obj ) ? [] : !obj.constructor ? {} : new obj.constructor(); if( obj instanceof Map ) for( var key of obj.keys() ) result.set( key, deepClone( obj.get( key ) ) ); for( var key in obj ) if( obj.hasOwnProperty( key ) ) result[key] = deepClone( obj[ key ] ); return result; }
это работает для массивов, объектов и примитивов. Дважды рекурсивный алгоритм, который переключается между двумя методами обхода:
const deepClone = (objOrArray) => { const copyArray = (arr) => { let arrayResult = []; arr.forEach(el => { arrayResult.push(cloneObjOrArray(el)); }); return arrayResult; } const copyObj = (obj) => { let objResult = {}; for (key in obj) { if (obj.hasOwnProperty(key)) { objResult[key] = cloneObjOrArray(obj[key]); } } return objResult; } const cloneObjOrArray = (el) => { if (Array.isArray(el)) { return copyArray(el); } else if (typeof el === 'object') { return copyObj(el); } else { return el; } } return cloneObjOrArray(objOrArray); }
мы можем использовать рекурсию для создания deepCopy. Он может создать копию массива, объекта, массива объекта, объекта с функцией. если вы хотите, вы можете добавить функцию для другой тип структуры данных, такие как карты и т. д.
function deepClone(obj) { var retObj; _assignProps = function(obj, keyIndex, retObj) { var subType = Object.prototype.toString.call(obj[keyIndex]); if(subType === "[object Object]" || subType === "[object Array]") { retObj[keyIndex] = deepClone(obj[keyIndex]); } else { retObj[keyIndex] = obj[keyIndex]; } }; if(Object.prototype.toString.call(obj) === "[object Object]") { retObj = {}; for(key in obj) { this._assignProps(obj, key, retObj); } } else if(Object.prototype.toString.call(obj) == "[object Array]") { retObj = []; for(var i = 0; i< obj.length; i++) { this._assignProps(obj, i, retObj); } }; return retObj; };
в реальном мире больше не должно быть необходимости в такой функции. Это чисто академический интерес.
как чисто упражнение, это более функциональный способ сделать это. Это расширение @tfmontague ответ как Я бы предложил добавление защитного блока там. Но, видя, как я чувствую себя вынужденным ES6 и функционализировать все вещи, вот моя сутенерская версия. Это усложняет логику, так как вам нужно сопоставить массив и уменьшить над объектом, но он избегает любых мутаций.
function cloner(x) { const recurseObj = x => typeof x === 'object' ? cloner(x) : x const cloneObj = (y, k) => { y[k] = recurseObj(x[k]) return y } // Guard blocks // Add extra for Date / RegExp if you want if (!x) { return x } if (Array.isArray(x)) { return x.map(recurseObj) } return Object.keys(x).reduce(cloneObj, {}) } const tests = [ null, [], {}, [1,2,3], [1,2,3, null], [1,2,3, null, {}], [new Date('2001-01-01')], // FAIL doesn't work with Date {x:'', y: {yx: 'zz', yy: null}, z: [1,2,3,null]}, { obj : new function() { this.name = "Object test"; } } // FAIL doesn't handle functions ] tests.map((x,i) => console.log(i, cloner(x)))
вот самое элегантное решение для глубокого копирования, которое я мог бы придумать без использования фреймворка (синтаксис ES6).
наслаждайтесь!
/** * Deep copy an array or object * @param {array|object} a */ const deepCopy = (a) => { if (Array.isArray(a)) return [...a]; if (a !== null && typeof a === 'object') return Object.assign({}, a); return a; }
Comments