elemento - javascript unir dos objetos
¿Cuál es la forma más eficiente de clonar en profundidad un objeto en JavaScript? (30)
¿Cuál es la forma más eficiente de clonar un objeto JavaScript? He visto obj = eval(uneval(o));
se utiliza, pero eso no es estándar y solo es compatible con Firefox .
He hecho cosas como obj = JSON.parse(JSON.stringify(o));
Pero cuestiona la eficiencia.
También he visto funciones de copia recursiva con varios defectos.
Me sorprende que no exista una solución canónica.
Nota: Esta es una respuesta a otra respuesta, no una respuesta adecuada a esta pregunta. Si desea una clonación rápida de objetos, siga los consejos de Corban en su respuesta a esta pregunta.
Quiero señalar que el método .clone()
en jQuery solo clona elementos DOM. Para clonar objetos JavaScript, deberías hacer:
// Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
Se puede encontrar más información en la documentación de jQuery .
También quiero señalar que la copia profunda es en realidad mucho más inteligente que lo que se muestra arriba: puede evitar muchas trampas (por ejemplo, tratar de extender en profundidad un elemento DOM). Se usa con frecuencia en jQuery core y en complementos para un gran efecto.
Clonación Estructurada
HTML5 define un algoritmo de clonación interno "estructurado" que puede crear clones profundos de objetos. Todavía está limitado a ciertos tipos incorporados, pero además de los pocos tipos compatibles con JSON, también admite fechas, RegExps, mapas, conjuntos, blobs, listas de archivos, ImageDatas, arrays dispersos, arrays tipados y probablemente más en el futuro. . También conserva las referencias dentro de los datos clonados, lo que le permite soportar estructuras cíclicas y recursivas que podrían causar errores para JSON.
Soporte directo en los navegadores: ¿Próximamente? 🙂
Actualmente, los navegadores no proporcionan una interfaz directa para el algoritmo de clonación estructurada, pero se está discutiendo activamente una función global structuredClone()
en whatwg / html # 793 en GitHub, ¡ y puede que esté disponible próximamente! Tal como se propone actualmente, su uso para la mayoría de los propósitos será tan simple como:
const clone = structuredClone(original);
Hasta que se envíe, las implementaciones de clones estructurados de los navegadores solo se exponen de manera indirecta.
Solución asíncrona: utilizable. 😕
La forma más económica de crear un clon estructurado con las API existentes es publicar los datos a través de un puerto de MessageChannels . El otro puerto emitirá un evento de message
con un clon estructurado de los datos adjuntos. Desafortunadamente, escuchar estos eventos es necesariamente asíncrono y las alternativas sincrónicas son menos prácticas.
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);
Ejemplo de uso:
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();
Soluciones sincrónicas: horrible! 🤢
No hay buenas opciones para crear clones estructurados sincrónicamente. Aquí hay un par de hacks poco prácticos en su lugar.
history.pushState()
y history.replaceState()
crean un clon estructurado de su primer argumento y asignan ese valor a history.state
. Puedes usar esto para crear un clon estructurado de cualquier objeto como este:
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
Ejemplo de uso:
''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();
Aunque sincrónico, esto puede ser extremadamente lento. Incurre en todos los gastos generales asociados con la manipulación del historial del navegador. Llamar a este método repetidamente puede hacer que Chrome deje de responder temporalmente.
El constructor de Notification
crea un clon estructurado de sus datos asociados. También intenta mostrar una notificación del navegador al usuario, pero esto fallará silenciosamente a menos que haya solicitado el permiso de notificación. En caso de que tenga permiso para otros fines, cerraremos inmediatamente la notificación que hemos creado.
const structuredClone = obj => {
const n = new Notification('''', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
Ejemplo de uso:
''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();
La forma eficiente de clonar (no clonar en profundidad) un objeto en una línea de código
Un método Object.assign
es parte del estándar ECMAScript 2015 (ES6) y hace exactamente lo que necesita.
var clone = Object.assign({}, obj);
El método Object.assign () se utiliza para copiar los valores de todas las propiedades propias enumerables de uno o más objetos de origen a un objeto de destino.
El polyfill para soportar navegadores más antiguos:
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;
}
});
}
AngularJS
Bueno, si estás usando angular, también podrías hacer esto.
var newObject = angular.copy(oldObject);
Aquí hay una versión de la respuesta de ConroyP anterior que funciona incluso si el constructor ha requerido parámetros:
//If Object.create isn''t already defined, we just do the simple shim,
//without the second argument, since that''s all we need here
var object_create = Object.create;
if (typeof object_create !== ''function'') {
object_create = function(o) {
function F() {}
F.prototype = o;
return new F();
};
}
function deepCopy(obj) {
if(obj == null || typeof(obj) !== ''object''){
return obj;
}
//make sure the returned object has the same prototype as the original
var ret = object_create(obj.constructor.prototype);
for(var key in obj){
ret[key] = deepCopy(obj[key]);
}
return ret;
}
Esta función también está disponible en mi biblioteca simpleoo .
Editar:
Aquí hay una versión más robusta (gracias a Justin McCandless, ahora también admite referencias cíclicas):
/**
* 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://.com/a/11621004/560114
*/
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
if(src === null || typeof(src) !== ''object''){
return src;
}
//Honor native/custom clone methods
if(typeof src.clone == ''function''){
return src.clone(true);
}
//Special cases:
//Date
if(src instanceof Date){
return new Date(src.getTime());
}
//RegExp
if(src instanceof RegExp){
return new RegExp(src);
}
//DOM Element
if(src.nodeType && typeof src.cloneNode == ''function''){
return src.cloneNode(true);
}
// Initialize the visited objects arrays if needed.
// This is used to detect cyclic references.
if (_visited === undefined){
_visited = [];
_copiesVisited = [];
}
// Check if this object has already been visited
var i, len = _visited.length;
for (i = 0; i < len; i++) {
// If so, get the copy we already made
if (src === _visited[i]) {
return _copiesVisited[i];
}
}
//Array
if (Object.prototype.toString.call(src) == ''[object Array]'') {
//[].slice() by itself would soft clone
var ret = src.slice();
//add it to the visited array
_visited.push(src);
_copiesVisited.push(ret);
var i = ret.length;
while (i--) {
ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
}
return ret;
}
//If we''ve reached here, we have a regular object
//make sure the returned object has the same prototype as the original
var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
if (!proto) {
proto = src.constructor.prototype; //this line would probably only be reached by very old browsers
}
var dest = object_create(proto);
//add this object to the visited array
_visited.push(src);
_copiesVisited.push(dest);
for (var key in src) {
//Note: this does NOT preserve ES5 property attributes like ''writable'', ''enumerable'', etc.
//For an example of how this could be modified to do so, see the singleMixin() function
dest[key] = deepCopy(src[key], _visited, _copiesVisited);
}
return dest;
}
//If Object.create isn''t already defined, we just do the simple shim,
//without the second argument, since that''s all we need here
var object_create = Object.create;
if (typeof object_create !== ''function'') {
object_create = function(o) {
function F() {}
F.prototype = o;
return new F();
};
}
Código:
// 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;
}
Prueba:
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);
Compruebe este punto de referencia: http://jsben.ch/#/bWfk9
En mis pruebas anteriores, donde la velocidad era una de las principales preocupaciones que encontré
JSON.parse(JSON.stringify(obj))
para ser la forma más rápida de clonar en profundidad un objeto (supera a jQuery.extend con la jQuery.extend profunda establecida en un 10-20%).
jQuery.extend es bastante rápido cuando la marca de profundidad se establece en falso (clon superficial). Es una buena opción, ya que incluye un poco de lógica adicional para la validación de tipos y no copia propiedades no definidas, etc., pero esto también lo desacelerará un poco.
Si conoce la estructura de los objetos que está intentando clonar o puede evitar las matrices anidadas profundas, puede escribir un bucle simple for (var i in obj)
para clonar su objeto mientras comprueba hasOwnProperty y será mucho más rápido que jQuery.
Por último, si está intentando clonar una estructura de objeto conocida en un bucle activo, puede obtener MUCHO MÁS RENDIMIENTO simplemente alineando el procedimiento de clonación y construyendo manualmente el objeto.
Los motores de traza de JavaScript apestan al optimizar para for..in
bucles y verificar que hasOwnProperty también lo hará más lento. Clon manual cuando la velocidad es una necesidad absoluta.
var clonedObject = {
knownProp: obj.knownProp,
..
}
Tenga cuidado al usar el JSON.parse(JSON.stringify(obj))
en los objetos Date
- JSON.stringify(new Date())
devuelve una representación de cadena de la fecha en formato ISO, que JSON.parse()
no vuelve a convertir a un objeto Date
. Ver esta respuesta para más detalles .
Además, tenga en cuenta que, al menos en Chrome 65, la clonación nativa no es el camino a seguir. De acuerdo con este JSPerf , realizar una clonación nativa al crear una nueva función es casi 800x más lento que usar JSON.stringify, que es increíblemente rápido en todos los ámbitos.
Esto es lo que estoy usando:
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;
}
Hay una biblioteca (llamada "clon") , que hace esto bastante bien. Proporciona la clonación / copia recursiva más completa de objetos arbitrarios que conozco. También admite referencias circulares, que aún no están cubiertas por las otras respuestas.
Puedes encontrarlo en npm , también. Se puede utilizar para el navegador y para Node.js.
Aquí hay un ejemplo de cómo usarlo:
Instalarlo con
npm install clone
o empaquetarlo con Ender .
ender build clone [...]
También puede descargar el código fuente manualmente.
Entonces puedes usarlo en tu código fuente.
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'' } }
(Descargo de responsabilidad: Soy el autor de la biblioteca.)
Sé que este es un post antiguo, pero pensé que esto podría ser de alguna ayuda para la siguiente persona que tropieza.
Mientras no asigne un objeto a nada, no mantiene ninguna referencia en la memoria. Entonces, para hacer un objeto que quieras compartir entre otros objetos, tendrás que crear una fábrica como esta:
var a = function(){
return {
father:''zacharias''
};
},
b = a(),
c = a();
c.father = ''johndoe'';
alert(b.father);
Si lo está utilizando, la biblioteca Underscore.js tiene un método de clone .
var newObject = _.clone(oldObject);
Si no hubiera ninguna incorporada, puedes intentar:
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;
}
Suponiendo que solo tiene variables y no funciones en su objeto, solo puede usar:
var newObject = JSON.parse(JSON.stringify(oldObject));
Cloning
un objeto siempre fue una preocupación en JS, pero se trataba de ES6, enumero diferentes formas de copiar un objeto en JavaScript a continuación, imagina que tienes el objeto a continuación y te gustaría tener una copia detallada de eso:
var obj = {a:1, b:2, c:3, d:4};
Hay algunas formas de copiar este objeto, sin cambiar el origen:
1) ES5 +, utilizando una función simple para hacer la copia por usted:
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 +, utilizando JSON.parse y 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
Espero que esta ayuda ...
Copia profunda de objetos en JavaScript (creo que lo mejor y lo más simple)
1. Usando JSON.parse (JSON.stringify (objeto));
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.Utilizando el método creado.
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. Usando el enlace lodashLo-Dash en 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. Usando el método 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 }
Pero mal cuando
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.Utilizando 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 }
Pero mal cuando
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 Performance Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd
Copia profunda por desempeño: clasificada de mejor a peor
- Reasignación "=" (matrices de cadenas, matrices de números, solo)
- Rebanada (matrices de cadenas, matrices de números - solamente)
- Concatenación (matrices de cadenas, matrices de números - solamente)
- Función personalizada: for-loop o copia recursiva.
- $. extensión de jQuery
- JSON.parse (arrays de cadenas, arrays de números, arrays de objetos - solo)
- _Clone de Underscore.js (matrices de cadenas, matrices de números - solo)
- Lo-Dash''s _.cloneDeep
Copie en profundidad una matriz de cadenas o números (un nivel, sin punteros de referencia):
Cuando una matriz contiene números y cadenas: funciones como .slice (), .concat (), .splice (), el operador de asignación "=" y la función de clonación de Underscore.js; Hará una copia en profundidad de los elementos de la matriz.
Donde la reasignación tiene el rendimiento más rápido:
var arr1 = [''a'', ''b'', ''c''];
var arr2 = arr1;
arr1 = [''a'', ''b'', ''c''];
Y .slice () tiene mejor rendimiento que .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
Copie en profundidad una matriz de objetos (dos o más niveles - punteros de referencia):
var arr1 = [{object:''a''}, {object:''b''}];
Escriba una función personalizada (tiene un rendimiento más rápido que $ .extend () o 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);
Utilice funciones de utilidad de terceros:
$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash
Donde jQuery $ .extend tiene un mejor rendimiento:
Crockford sugiere (y yo prefiero) usar esta función:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var newObject = object(oldObject);
Es escueto, funciona como se espera y no necesita una biblioteca.
EDITAR:
Este es un polyfill para Object.create
, por lo que también puede usar esto.
var newObject = Object.create(oldObject);
NOTA: Si usa algo de esto, puede tener problemas con alguna iteración que usen hasOwnProperty
. Porque, create
crea un nuevo objeto vacío que hereda oldObject
. Pero sigue siendo útil y práctico para clonar objetos.
Por ejemplo si oldObject.a = 5;
newObject.a; // is 5
pero:
oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
Tengo dos buenas respuestas dependiendo de si su objetivo es clonar un "objeto JavaScript antiguo y simple" o no.
Supongamos también que su intención es crear un clon completo sin referencias de prototipos al objeto de origen. Si no está interesado en un clon completo, entonces puede usar muchas de las rutinas de Object.clone () proporcionadas en algunas de las otras respuestas (patrón de Crockford)
Para los objetos JavaScript simples y antiguos, una buena y probada forma de clonar un objeto en tiempos de ejecución modernos es muy simple:
var clone = JSON.parse(JSON.stringify(obj));
Tenga en cuenta que el objeto fuente debe ser un objeto JSON puro. Es decir, todas sus propiedades anidadas deben ser escalares (como booleano, cadena, matriz, objeto, etc.). Cualquier función u objeto especial como RegExp o Date no será clonado.
¿Es eficiente? Heck sí. Hemos intentado todo tipo de métodos de clonación y esto funciona mejor. Estoy seguro de que algún ninja podría conjurar un método más rápido. Pero sospecho que estamos hablando de ganancias marginales.
Este enfoque es simple y fácil de implementar. Envuélvalo en una función de conveniencia y si realmente necesita obtener algo de ganancia, inténtelo más tarde.
Ahora, para los objetos JavaScript no planos, no hay una respuesta realmente simple. De hecho, no puede haberlo debido a la naturaleza dinámica de las funciones de JavaScript y el estado interno del objeto. La clonación profunda de una estructura JSON con funciones internas requiere que se vuelvan a crear esas funciones y su contexto interno. Y JavaScript simplemente no tiene una forma estandarizada de hacerlo.
La forma correcta de hacer esto, una vez más, es a través de un método conveniente que declara y reutiliza dentro de su código. El método de conveniencia se puede dotar de cierta comprensión de sus propios objetos para que pueda asegurarse de recrear correctamente el gráfico dentro del nuevo objeto.
Estamos escritos por nosotros mismos, pero el mejor enfoque general que he visto se trata aquí:
http://davidwalsh.name/javascript-clone
Esta es la idea correcta. El autor (David Walsh) ha comentado la clonación de funciones generalizadas. Esto es algo que puede elegir hacer, dependiendo de su caso de uso.
La idea principal es que necesita manejar de forma especial la creación de instancias de sus funciones (o clases de prototipos, por así decirlo) por tipo. Aquí, ha proporcionado algunos ejemplos para RegExp y Date.
Este código no solo es breve, sino que también es muy legible. Es bastante fácil de extender.
¿Es esto eficiente? Heck sí. Dado que el objetivo es producir un verdadero clon de copia profunda, entonces tendrá que recorrer los miembros del gráfico del objeto de origen. Con este enfoque, puede modificar exactamente qué miembros secundarios tratar y cómo manejar manualmente los tipos personalizados.
Ahí vas. Dos enfoques. Ambos son eficientes en mi opinión.
Aquí hay un método clone () completo que puede clonar cualquier objeto JavaScript. Maneja casi todos los casos:
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;
};
Copia superficial de una sola línea ( ECMAScript 5ª edición ):
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
Y una copia superficial de una sola línea ( ECMAScript 6ª edición , 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
Esta no es generalmente la solución más eficiente, pero hace lo que necesito. Casos de prueba simples a continuación ...
function clone(obj, clones) {
// Makes a deep copy of ''obj''. Handles cyclic structures by
// tracking cloned obj''s in the ''clones'' parameter. Functions
// are included, but not cloned. Functions members are cloned.
var new_obj,
already_cloned,
t = typeof obj,
i = 0,
l,
pair;
clones = clones || [];
if (obj === null) {
return obj;
}
if (t === "object" || t === "function") {
// check to see if we''ve already cloned obj
for (i = 0, l = clones.length; i < l; i++) {
pair = clones[i];
if (pair[0] === obj) {
already_cloned = pair[1];
break;
}
}
if (already_cloned) {
return already_cloned;
} else {
if (t === "object") { // create new object
new_obj = new obj.constructor();
} else { // Just use functions as is
new_obj = obj;
}
clones.push([obj, new_obj]); // keep track of objects we''ve cloned
for (key in obj) { // clone object members
if (obj.hasOwnProperty(key)) {
new_obj[key] = clone(obj[key], clones);
}
}
}
}
return new_obj || obj;
}
Prueba de matriz cíclica ...
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
Prueba de funcionamiento...
f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false
Lo siguiente crea dos instancias del mismo objeto. Lo encontré y lo estoy usando actualmente. Es simple y fácil de usar.
var objToCreate = JSON.parse(JSON.stringify(cloneThis));
Lodash tiene un buen lodash.com/docs#cloneDeep :
var objects = [{ ''a'': 1 }, { ''b'': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
No estoy de acuerdo con la respuesta con los mejores votos here . Un Clon Profundo Recursivo es mucho más rápido que el enfoque JSON.parse (JSON.stringify (obj)) mencionado.
- Jsperf lo ubica como el número uno aquí: https://jsperf.com/deep-copy-vs-json-stringify-json-parse/5
- Jsben de la respuesta anterior se actualizó para mostrar que un clon profundo recursivo supera a todos los demás mencionados: http://jsben.ch/13YKQ
Y aquí está la función para una referencia rápida:
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
}
Para las personas que desean usar la JSON.parse(JSON.stringify(obj))
versión, pero sin perder los objetos de fecha, puede usar el segundo argumento del parse
método para convertir las cadenas de nuevo a fecha:
function clone(obj) {
var regExp = /^/d{4}-/d{2}-/d{2}T/d{2}:/d{2}:/d{2}/./d{3}Z$/;
return JSON.parse(JSON.stringify(x), function(k, v) {
if (typeof v === ''string'' && regExp.test(v))
return new Date(v);
return v;
});
}
Parece que no hay un operador de clonación profunda ideal para los objetos de tipo matriz. Como se ilustra en el siguiente código, el clonador jQuery de John Resig convierte los arreglos con propiedades no numéricas en objetos que no son arreglos, y el clonador JSON de RegDwight elimina las propiedades no numéricas. Las siguientes pruebas ilustran estos puntos en múltiples navegadores:
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)
Solo porque no vi a AngularJS mencionado y pensé que la gente podría querer saber ...
angular.copy
También proporciona un método de copia profunda de objetos y matrices.
// 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;
};
function clone(obj)
{ var clone = {};
clone.prototype = obj.prototype;
for (property in obj) clone[property] = obj[property];
return clone;
}
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});