-
-
Save lxyd/0d3e6ce689e76105f3ef to your computer and use it in GitHub Desktop.
JS deep clone function that survives multiframe environment, handles loops and supports plain hashes, Arrays, Dates and standard object wrappers (Boolean, Number, String). All that in 323 bytes minified. Tested in IE8, Firefox, Chromium and Opera
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function clone( | |
o // The only argument passed by user: object to clone | |
,h // Cache (organized as array: [key,value,key,value,...]) | |
) { | |
var i,r,x // Property indexer, result, temporary variable | |
,t=[Array,Date,Number,String,Boolean] // Types to treat in a special way | |
,s=Object.prototype.toString; // Shortcut to Object.prototype.toString | |
h = h || []; // If cache is not created yet, create it! | |
for(i=0; i < h.length; i+=2) // Search cache for our object | |
if(o === h[i]) | |
r = h[i+1]; | |
if(!r && o && typeof o == "object") { // Clone o if it is uncached object and not null | |
r={}; // Default result template: plain hash | |
for(i=0; i < t.length; i++) // To handle multiframe environment, search for type by | |
if(s.call(o)==s.call( // comparing Object.prototype.toString's of our object | |
x=new t[i](o))) // and new object x created with the constructor t[i] | |
// Notice that it will create new Date(o), new String(o) | |
// which is good and new Array(o) which is bad | |
r = i ? x : []; // If i==0, t==Array. We need to recreate it. Else use x | |
h.push(o,r); // Add object to cache before (!) making recursive call | |
for(i in o) // Just copy properties recoursively | |
if(h.hasOwnProperty.call(o, i)) // As o might have key 'hasOwnProperty', use something | |
r[i] = clone(o[i], h) // we defined right instead | |
} | |
return r || o // Return r if it was found in cache or built in if(){} | |
} // otherwise, return the original object |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// faster and more reliable version with less conversions/calculations | |
var clone=(function( // Create a closure with precalculated t | |
t // t is a map Class->String organized as | |
// an array [Cls,"str",Cls,"str",...] | |
,s // Shortcut to Object.prototype.toString | |
,c // (placeholder) | |
){ | |
for(c=0;c<t.length;) // Fill t[] with string representations | |
t[c++]=s.call(new t[c++]()); // of standard types. Use new instead of | |
// t[i].prototype to support IE8 | |
return c=function( // Now, c is the main function | |
o // Object to clone (passed by user) | |
,h // Cache: map Object->Object organized | |
// as an array [key,value,key,value,...] | |
,i // (placeholder) iterator | |
,r // (placeholder) result | |
,x // (placeholder) string representation of o | |
){ | |
h=h||[]; // Create cache if it is not created yet | |
if(o&&typeof o=="object"){ // If o is object and is not null, clone | |
for(i=0;i<h.length;i++) // Try to find the object in cache | |
if(h[i]===o) | |
return h[i+1]; | |
r={}; // Default result template: plain hash | |
x=s.call(o); // String representation to find out type | |
for(i=0;i<t.length;i+=2) // Try to find appropriate constructor | |
if(t[i]==x) // among default types | |
r=i?new t[i+1](o):[]; // i==0 <=> type is Array. Otherwise, use | |
// new type(o) (e.g. new Date(o)) | |
h.push(o,r); // Push to cache before(!) recursive calls | |
for(i in o) // Copy properties recursively | |
if(h.hasOwnProperty.call(o,i)) // o can contain key 'hasOwnProperty'. h can't | |
r[i]=c(o[i],h) // Here it is! The recursive call | |
} | |
return r||o // If r was created inside the if(){} block, | |
// return it. Otherwise return source object | |
} | |
})( | |
[,Array,,Date,,Number,,String,,Boolean] // Template for str->Cls map | |
,({}).toString // Shortcut to Object.prototype.toString | |
); | |
// minified 398 bytes | |
var clone=(function(t,s,c){for(c=0;c<t.length;)t[c++]=s.call(new t[c++]());return c=function(o,h,i,r,x){h=h||[];if(o&&typeof o=="object"){for(i=0;i<h.length;i++)if(h[i]===o)return h[i+1];r={};x=s.call(o);for(i=0;i<t.length;i+=2)if(t[i]==x)r=i?new t[i+1](o):[];h.push(o,r);for(i in o)if(h.hasOwnProperty.call(o,i))r[i]=c(o[i],h)}return r||o}})([,Array,,Date,,Number,,String,,Boolean],({}).toString); | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
Version 2, December 2004 | |
Copyright (C) 2012 lxyd <https://github.com/lxyd> | |
Everyone is permitted to copy and distribute verbatim or modified | |
copies of this license document, and changing it is allowed as long | |
as the name is changed. | |
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
0. You just DO WHAT THE FUCK YOU WANT TO. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function clone(o,h){var i,r,x,t=[Array,Date,Number,String,Boolean],s=({}).toString;h=h||[];for(i=0;i<h.length;i+=2)if(o===h[i])r=h[i+1];if(!r&&o&&typeof o=="object"){r={};for(i=0;i<t.length;i++)if(s.call(o)==s.call(x=new t[i](o)))r=i?x:[];h.push(o,r);for(i in o)if(s.hasOwnProperty.call(o,i))r[i]=clone(o[i],h)}return r||o} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html><head> | |
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>deep clone</title> | |
<!-- JSON serializer: for demonstration purposes only (it is not used in cloning) --> | |
<script>var JSON;if(!JSON){JSON={}}(function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];if(i&&typeof i==="object"&&typeof i.toJSON==="function"){i=i.toJSON(a)}if(typeof rep==="function"){i=rep.call(b,a,i)}switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i){return"null"}gap+=indent;h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1){h[c]=str(c,i)||"null"}e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]";gap=g;return e}if(rep&&typeof rep==="object"){f=rep.length;for(c=0;c<f;c+=1){if(typeof rep[c]==="string"){d=rep[c];e=str(d,i);if(e){h.push(quote(d)+(gap?": ":":")+e)}}}}else{for(d in i){if(Object.prototype.hasOwnProperty.call(i,d)){e=str(d,i);if(e){h.push(quote(d)+(gap?": ":":")+e)}}}}e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}";gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b==="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function f(a){return a<10?"0"+a:a}"use strict";if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;if(typeof JSON.stringify!=="function"){JSON.stringify=function(a,b,c){var d;gap="";indent="";if(typeof c==="number"){for(d=0;d<c;d+=1){indent+=" "}}else if(typeof c==="string"){indent=c}rep=b;if(b&&typeof b!=="function"&&(typeof b!=="object"||typeof b.length!=="number")){throw new Error("JSON.stringify")}return str("",{"":a})}}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e==="object"){for(c in e){if(Object.prototype.hasOwnProperty.call(e,c)){d=walk(e,c);if(d!==undefined){e[c]=d}else{delete e[c]}}}}return reviver.call(a,b,e)}var j;text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}})()</script> | |
<script> | |
// here it is: | |
function clone(o,h){var i,r,x,t=[Array,Date,Number,String,Boolean],s=({}).toString;h=h||[];for(i=0;i<h.length;i+=2)if(o===h[i])r=h[i+1];if(!r&&o&&typeof o=="object"){r={};for(i=0;i<t.length;i++)if(s.call(o)==s.call(x=new t[i](o)))r=i?x:[];h.push(o,r);for(i in o)if(s.hasOwnProperty.call(o,i))r[i]=clone(o[i],h)}return r||o} | |
var a,b,i; | |
a = { | |
f1: [ | |
{f11:0, f12:null, f13:[]}, | |
345, | |
null, | |
new Date() | |
], | |
f2: { | |
f21:{}, | |
f22:null, | |
f23:[] | |
} | |
} | |
document.write('<br/><h3>The following two objects must be equal:</h3>'); | |
document.write(JSON.stringify(a) + "<br/>"); | |
b = clone(a); | |
document.write(JSON.stringify(b) + "<br/>"); | |
b.f1[0].f11 = 100; | |
b.f1.push(123); | |
b.f1[3].setHours(b.f1[3].getHours()+12); | |
document.write('<br/><h3>The following two objects must NOT be equal:</h3>'); | |
document.write(JSON.stringify(a) + "<br/>"); | |
document.write(JSON.stringify(b) + "<br/>"); | |
document.write('<br/><h3>Adding cyclic references:</h3>'); | |
document.write('a.f3 = a;<br/>'); | |
a.f3 = a; | |
document.write('a.f1[4] = a.f2;<br/>'); | |
a.f1[4] = a.f2; | |
document.write('b = clone(a);<br/>'); | |
b = clone(a); | |
document.write('b.f3 === b: ' + (b.f3===b) + '<br/>'); | |
document.write('b.f1[4] === b.f2: ' + (b.f1[4] === b.f2) + '<br/>'); | |
(function(undef) { | |
a = [new Date(), "asdf", "", 12, -3.4, 0, 1/0, parseInt('asdf'), true, false, null, undef] | |
})(); | |
document.write('<br/><h3>Cloning Dates, strings, numbers, boolean, null, undefined:</h3>'); | |
for(i=0;i<a.length;i++){ | |
document.write(a[i] + " -> " + clone(a[i]) + "<br/>"); | |
}; | |
</script></head><body></body></html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment