I'm trying to write an inheritence logic where I clone an input class and inherit from remaining parent classes. In order to do that, I need to create a new class, deep copying one of the classes exactly. I'm trying to achieve something like:
class Original {static b=1; static test2(){}; test(){}}
var CopyClass = DeepCopySomehow(Original)
class NotWorking extends Original{}
console.log(Object.getOwnPropertyNames(NotWorking))
// [ 'length', 'name', 'prototype']
console.log(Object.getOwnPropertyNames(Original))
// [ 'length', 'name', 'prototype', 'test2', 'b' ]
console.log(Object.getOwnPropertyNames(NotWorking.prototype))
// ['constructor']
console.log(Object.getOwnPropertyNames(Class.prototype))
// ['constructor', 'test']
I have something like (simplified):
function inherit(Class1, Class2) {
class Base extends Class1 {...stuff...}
Object.defineProperty(Base, 'name', {value: Class1.name});
copy_props(Base.prototype, Class1.prototype);
copy_props(Base, Class1.prototype);
copy_props(Base.prototype, Class2.prototype);
copy_props(Base, Class2.prototype);
}
however, this still keeps the information of "Base" somehow.
Browser side -- Here is a reproducable example:
class SpecificParentName{constructor() {return new Proxy(this, {})}}
const Base = class extends SpecificParentName{constructor(...args){super(...args)}}
Base.toString = () => SpecificParentName.toString()
Object.defineProperty(Base, 'name', {value: SpecificParentName.name});
console.log(Base)
// class extends SpecificParentName{constructor(...args){super(...args)}}
// reasonable output, although I would have wanted it to be just class SpecificParentName if possible
console.log(new Base())
// Proxy(Base) {} // definitely not desired, because it doesn't point to SpecificParentName
console.log(new Proxy(Base, {}))
// Proxy(Function) {length: 0, name: 'SpecificParentName', prototype: SpecificParentName}
// it's ok since points to SpecificParentName
Nodejs side -- I also had a similar problem in nodejs side before:
class SpecificParentName{}
console.log(SpecificParentName)
// "[class SpecificParentName]"
const Base = class extends SpecificParentName{}
console.log(Base)
// [class Base extends SpecificParentName]
// I'd like this^ to be just "[class SpecificParentName]"
// hacky fix on nodejs:
const Base2 = class extends SpecificParentName{
static [require('util').inspect.custom]() {return `[class ${SpecificParentName.name}]`}
console.log(Base2)
// "[class SpecificParentName]"
}
so my question is, why and how javascript knows about the variable name I use when defining a class when printing, and is there a way to customize it?
Q. "How does JavaScript "decide" how to print a class name?"
Firstly, every function (including class constructor function) has an own name
property.
Secondly, how this name is going to be exposed is part of the function-type specific toString
implementation. In case of instances of built-in types the class name gets additionally exposed by the initial Symbol.toStringTag
implementation. The latter behavior is not shown by instances of custom class implementations (theirs default always returns '[object Object]'
).
Last, both implementation can be overwritten; and as for function names (class constructor functions included), any unnamed/anonymous function or class expression that gets assigned to a variable or property does get assigned this variable's or property's name to its own name
property.
Thus, everything the OP wants to achieve can be done through a pattern of dynamic subclassing/sub-typing, implemented as single factory function which creates a named subclass.
Said function does expect 3 parameters ...
the later to be exposed class name
toString
method,Object.prototype.toString.call(subclassInstance)
the to be extended super or base class
an optional initializer function which to a certain extent does cover the functionality of a constructor function.
Example code ...
class SpecificParentName {}
console.log({
SpecificParentName,
'SpecificParentName.name': SpecificParentName.name,
});
/*
* ... OP's request ...
*/
const Base = createNamedSubclass('SpecificParentName', SpecificParentName);
console.log({
Base,
'Base.name': Base.name,
});
const baseInstance = new Base;
console.log(
'Object.prototype.toString.call(baseInstance) ...',
Object.prototype.toString.call(baseInstance)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
function createNamedSubclass(
className = 'UnnamedType' ,
baseClass = Object,
initializer = (...args) => args,
) {
const subClass = ({
// - ensures the class constructor's name.
[className]: class extends baseClass {
constructor(...args) {
super(...args);
initializer.apply(this, args);
}
},
})[className];
// - responsible for exposing the constructor's intended stringification.
Reflect.defineProperty(subClass, 'toString', {
value: () => `class ${ className }{}]`,
});
// - responsible for exposing an instance' class implementation detail.
Reflect.defineProperty(subClass.prototype, Symbol.toStringTag, {
get: () => className,
});
return subClass;
}
</script>
One could dive deeper into the dynamic subclassing/sub-typing matter by reading e.g. "How to dynamically create a subclass from a given class and enhance/augment the subclass constructor's prototype?"