based on https://stackoverflow.com/a/13635956/999 with some adjustments
Created
August 4, 2020 11:11
-
-
Save BlaM/4037566bdb1ad2ad747df54b23bd7c26 to your computer and use it in GitHub Desktop.
deepcopy.js
This file contains hidden or 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
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