Skip to content

Instantly share code, notes, and snippets.

@BlaM
Created August 4, 2020 11:11
Show Gist options
  • Save BlaM/4037566bdb1ad2ad747df54b23bd7c26 to your computer and use it in GitHub Desktop.
Save BlaM/4037566bdb1ad2ad747df54b23bd7c26 to your computer and use it in GitHub Desktop.
deepcopy.js
module.exports = deepCopy;
/**
* 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) {
//console.log(typeof src, JSON.stringify(src));
if (src === null || typeof src !== 'object') {
//console.log('... is not an object');
return src;
}
//Honor native/custom clone methods
if (typeof src.clone == 'function') {
//console.log('... has a clone method');
return src.clone(true);
}
//Special cases:
//Date
if (src instanceof Date) {
//console.log('... is a date object');
return new Date(src.getTime());
}
//RegExp
if (src instanceof RegExp) {
//console.log('... is a regular expression object');
return new RegExp(src);
}
//DOM Element
if (src.nodeType && typeof src.cloneNode == 'function') {
//console.log('... is a dom element');
return src.cloneNode(true);
}
if (src instanceof Location || (typeof src.href === 'string' && typeof src.protocol === 'string' && typeof src.pathname === 'string' && typeof src.host === 'string')) {
//console.log('... is a location object');
var A = document.createElement('A');
A.href = src.href;
return A;
}
// 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]) {
//console.log('... was already copied, so we quit to avoid an infinite loop');
return _copiesVisited[i];
}
}
//Array
if (Object.prototype.toString.call(src) == '[object Array]') {
//console.log('... is an 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;
//console.log('... looping through', src);
while (i--) {
ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
}
//console.log('... done looping through', src);
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);
var sval, readable = true;
//console.log('... is a generic object');
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
try {
sval = src[key];
} catch (x) {
readable = false;
// console.debug('cannot clone', key)
}
if (readable) {
//console.log('... descending into child', key, sval);
dest[key] = deepCopy(sval, _visited, _copiesVisited);
//console.log('... coming back child from', key);
} else {
readable = true;
}
}
//console.log('');
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();
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment