let dirtyDeepClone = (function(){
// Create a non-colliding variable name
// for an array that will hold functions.
let alfUUID = "alf_" + makeUUID();
// Create a new script element.
let scriptEl = document.createElement('SCRIPT');
// Add a non-colliding, object declaration
// to that new script element's text.
scriptEl.text = alfUUID + " = [];";
// Append the new script element to the document's body
document.body.append(scriptEl);
// The function that does the magic
function dirtyDeepClone(class_or_function){
if(typeof class_or_function !== "function"){
console.log("wrong input type");
return false;
}
let stringVersion = class_or_function.toString();
let newFunction = alfUUID + '.push(' + stringVersion + ')';
let funScript = document.createElement("SCRIPT");
funScript.text = newFunction;
document.body.append(funScript);
funScript.remove();
let last = window[alfUUID].length-1;
window[alfUUID][last] = extras(true, class_or_function, window[alfUUID][last]);
window[alfUUID][last].prototype = class_or_function.prototype;
return window[alfUUID][last];
}
////////////////////////////////////////////////
// SUPPORT FUNCTIONS FOR dirtyDeepClone FUNCTION
function makeUUID(){
// uuid adapted from: https://stackoverflow.com/a/21963136
var lut = [];
for (var i=0; i<256; i++)
lut[i] = (i<16?'0':'')+(i).toString(16);
var d0 = Math.random()*0xffffffff|0;
var d1 = Math.random()*0xffffffff|0;
var d2 = Math.random()*0xffffffff|0;
var d3 = Math.random()*0xffffffff|0;
var UUID = lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'_'+
lut[d1&0xff]+lut[d1>>8&0xff]+'_'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'_'+
lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'_'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+
lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff];
return UUID;
}
// Support variables for extras function
var errorConstructor = {
"Error":true,
"EvalError":true,
"RangeError":true,
"ReferenceError":true,
"SyntaxError":true,
"TypeError":true,
"URIError":true
};
var filledConstructor = {
"Boolean":true,
"Date":true,
"String":true,
"Number":true,
"RegExp":true
};
var arrayConstructorsES5 = {
"Array":true,
"BigInt64Array":true,
"BigUint64Array":true,
"Float32Array":true,
"Float64Array":true,
"Int8Array":true,
"Int16Array":true,
"Int32Array":true,
"Uint8Array":true,
"Uint8ClampedArray":true,
"Uint16Array":true,
"Uint32Array":true,
};
var filledConstructorES6 = {
"BigInt":true,
"Symbol":true
};
function extras(top, from, to){
// determine if obj is truthy
// and if obj is an object.
if(from !== null && (typeof from === "object" || top) && !from.isActiveClone){
// stifle further functions from entering this conditional
// (initially, top === true because we are expecting that to is a function)
top = false;
// if object was constructed
// handle inheritance,
// or utilize built-in constructors
if(from.constructor && !to){
let oType = from.constructor.name;
if(filledConstructor[oType])
to = new from.constructor(from);
else if(filledConstructorES6[oType])
to = from.constructor(from);
else if(from.cloneNode)
to = from.cloneNode(true);
else if(arrayConstructorsES5[oType])
to = new from.constructor(from.length);
else if ( errorConstructor[oType] ){
if(from.stack){
to = new from.constructor(from.message);
to.stack = from.stack;
}
else
to = new Error(from.message + " INACCURATE OR MISSING STACK-TRACE");
}
else // troublesome if constructor is poorly formed
to = new from.constructor();
}
else // loses cross-frame magic
to = Object.create(null);
let props = Object.getOwnPropertyNames(from);
let descriptor;
for(let i in props){
descriptor = Object.getOwnPropertyDescriptor( from, props[i] );
prop = props[i];
// recurse into descriptor, if necessary
// and assign prop to from
if(descriptor.value){
if(
descriptor.value !== null &&
typeof descriptor.value === "object" &&
typeof descriptor.value.constructor !== "function"
){
from.isActiveClone = true;
to[prop] = extras(false, from[prop]);
delete from.isActiveClone;
}
else
to[prop] = from[prop];
}
else
Object.defineProperty( to, prop, descriptor );
}
}
else if(typeof from === "function")
return dirtyDeepClone(from);
return from;
}
return dirtyDeepClone;
})();
// TESTS
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // call the super class constructor and pass in the name parameter
}
speak() {
console.log(`${this.name} barks.`);
}
}
function aFunc(x){console.log(x);}
aFunc.g = "h";
aFunc.Fun = function(){this.a = "b";}
let newFunc = dirtyDeepClone(aFunc);
newFunc("y");
let deepNewFunc = new newFunc.Fun();
console.log(deepNewFunc);
let newAni = dirtyDeepClone(Animal);
let nA = new newAni("person");
nA.speak();
let newDog = dirtyDeepClone(Dog);
let nD = new newDog("mutt");
nD.speak();
console.log({newFunc});
console.log({newAni});
console.log({newDog});