Skip to content

Instantly share code, notes, and snippets.

@lxyd
Created April 13, 2012 07:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lxyd/0d3e6ce689e76105f3ef to your computer and use it in GitHub Desktop.
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
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
// 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);
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.
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}
<!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] + " -&gt; " + 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