Accessing an Objects instance constructor for cloning - javascript

TLDR: Are there any pitfalls in using this.constructor(props) in a class method to duplicate that class?
I have a use case where defining class models for entities proves quite useful for management and processing throughout a complex system.
These entity model classes all require a set of common methods.
I do not wish to duplicate code across multiple entity model classes. I am exploring the use of a BaseEntityModel class which can be extended by all other model classes requiring the common functionality.
One of these methods is a clone() method, which provides a deep clone of the object as the ExtendedEntityModel type.
clone() will be declared on the BaseEntityModel class. I want to preserve the class name and properties when calling clone from a ExtendedEntityModel object which has extended BaseEntityModel class.
I need a way of creating a new instance of a ExtendedEntityModel through the BaseEntityModel without having to explicitly know what it is when calling clone(). In otherwords, I wanted to avoid using new ParentClassEntity() because the BaseEntityModel methods need to work for all extensions of it.
I did some searching but could not find much on this pattern. After some tinkering, I found that calling this.constructor(props) does exactly what I want. See below class, and clone() function
BaseEntityModel.js
class BaseEntityModel {
constructor({ entityType, dataKeys }) {
this.entityType = entityType;
this.dataKeys = dataKeys;
}
/**
* Provide deep clone of self, Preserving extended class properties
*/
clone() {
return new this.constructor(JSON.parse(JSON.stringify(this))); //<-- here, this.constructor
};
}
ExtendedModel.js
class ExtendedModel extends BaseEntityModel {
constructor({param1, param2} : ExtendedModel) {
super({ entityType, dataKeys });
this.param1 = param1;
this.param2 = param2;
}
}
and then usage
var extendedModel1 = new ExtendedModel({param1, param2};
var clonedExtendedModel = extendedModel1.clone(); //returns a cloned version and of type ExtendedModel
I cannot find much on calling this.constructor. Can anyone provide some insight into why this might be a bad choice, or confirm that this is not a javascript bomb waiting to go off?
See CodeSandbox of working implementation

Related

Call Class Constructor as Function on Instance

I am writing an object pool class that holds onto old unused objects, and when a new one is needed can use one of its reserves (Without the expensive creation of objects). However, I want to be able to save any data to this pool (not limited to something such as extends Poolable). The problem is that most object pools require something like this:
class MyData {
constructor(str) {
this.reset(str)
}
reset(str) {
this.str = str;
}
}
So that when a new instance is needed, the pool can call oldInstance.reset(prams). As stated before, I do not want this (Working with lots of third party tools that I am not feeling like writing wrappers for), so my data looks like this:
class MyData {
constructor(str) {
this.str = str;
}
}
When the pool needs to reset an instance, I need to be able to call the constructor as a function and set the this value to the instance that is being wiped.
This is super easy with old classes that used function/prototype syntax when they were made:
const MyClass = function(str) {
this.str = str;
}
// Reset an instance
const instance = new MyClass("foo");
MyClass.apply(instance, ["bar"])
// done
However when I do that for classes, it complains that you can not use a class without the new keyword. How do I go about doing this?
Fiddle: https://jsfiddle.net/wuqek405/
Edit:
As I said, most object pools need a reset function. I am trying to use the constructor as this function, because it is supposed to “set up” the instance. Another solution would be to generate this reset function based on the class. However, I want it to be as fast as possible, so terribly hacky solutions such as stringifying the class and evaling the constructor wouldn’t be optimal.
This is not possible. A class is not callable1, only constructable - which means creating a new object. This was new in ES6, which overhauled the inheritance model of classes and introduced super. Also, ES2022 will introduce class fields, which also get created during construction without being mentioned in the constructor code.
Your only option is to use only ES5 function-based classes, a transpiler, or writing explicit reset methods.
1: technically, it is callable (typeof C == 'function'), but [[Call]] will always throw an exception

Composition and Mixins with JavaScript

I am learning about compositions in Javascript. So I want to ask if this is correct way of doing things.
I made some exercises that look like this:
class Animal {
// constructor() {
// }
eat = () => {
console.log("this creature is eating");
}
}
const AnimalsWithWings = superclass => class extends superclass {
constructor(Feathers, ...args) {
super(...args);
Object.assign(this, { Feathers });
}
}
const CanDive = superclass => class extends superclass {
// constructor( ...args) {
// super(...args);
// }
dive = () => {
console.log("Can Dive");
}
}
class Duck extends AnimalsWithWings(CanDive(Animal)) {
constructor(eats, ...args) {
super(...args);
Object.assign(this, { eats });
}
}
const duffy = new Duck("watermelon", true);
console.log(duffy);
duffy.dive();
duffy.eat()
I am still in learning process so I just need some pointers.
Did it do more or less what you expected? Then, sure, it's a correct way to do it, whatever "correct" means here.
It looks to me like it does what it was meant to do when I pop it into the console. I can't really say much more on your code specifically because I'm not sure what concrete domain it's trying to model, aside from maybe breaking down Ducks into atomic pieces.
If you're going to do it this way, though, I'd personally prefer to use a params object instead of just changing the constructor signature like that with AnimalsWithWings. That way, the order of extra parametrizations doesn't depend on the order in which the mixins were applied, which I would consider a Surprise. Surprises are bad.
const AnimalsWithWings = superclass => class extends superclass {
// Everyone receives the same `params` object.
// They only take what they know about, and ignore the rest.
constructor(params) {
super(params);
Object.assign(this, { Feathers: params.Feathers });
}
}
Even more personal opiniony, I'd name them WithDiving and WithWings instead, just to keep a somewhat consistent naming scheme, and to better imply that these are modifiers, not "real" base classes.
Your code does saddle every Duck with a prototype chain 4 prototypes long, but eh, whatever. If it somehow becomes a performance problem then you can create a utility function to optimize the mixin process or something. Flatten the prototypes, maybe.
Your code does also let you call super.method() in methods, though it's debatable whether you should ever use that in a mixin at all. I'd say you shouldn't, unless you want your mixins to implicitly depend on each other, which is a Surprise.
There are plenty of other ways of doing mixins, too.
You could create a utility function to flatten all the prototypes into a single new one and return a base class from that which you extend. (Just be sure to iterate property descriptors rather than just using Object.assign() when doing that flattening, if you want to properly handle things like get/set accessors, etc.)
You could eschew Classes and just directly create prototype objects and use Object.create() to create instances. (same thing about iterating property descriptors.)
You could create a Duck prototype using a bunch of iterative calls to Object.create() instead of iteratively extending base classes.
You could control the additional behaviors with helper Controller Classes instead of composing behavior directly into the base.
You could deal just in plain objects with data, and pass the objects to functions that expect the object to have certain properties on it in order to do things. (Amusingly, called "duck typing") I'll grant that's not really mixins, just calling functions, but if it accomplishes the same thing in effect...
Probably a bunch others I can't really think about at the moment. It's all sticking sets of behaviors onto some base thing.

Augment all objects with another class properties/methods

Imagine I have two classes
class Point {
}
class EditablePoint extends Point {
// list of additional properties and methods
}
function edit(editablePoint) {
//
}
let point = new Point();
edit(point);
How can I augment all objects with another class methods/properties?
Any prototype hacks don't work for me. It seems I can't get around this instead of recreating the objects or assigning manually additional properties of the class or using a loop. I would like some one line clean solution.
Disclaimer: this looks like an antipattern with which I agree however I have huge arrays of Points and only in super rare occassions I need to edit them and I want to do so in-place without introducing any copying as arrays are HUGE.
Why I don't like having all points have editable properties and want to have them on the prototype:
each object instance becomes larger and that's the least I want
it doesnt communicate well to the code reader
What you're asking is technically feasible:
let pointProto = Point.prototype;
let editProto = EditablePoint.prototype;
for (let memberName in editProto) {
if (typeof editProto[memberName] === 'function'
&& !(memberName in pointProto)) {
pointProto[memberName] = editProto[memberName];
}
}
This will achieve what you're describing: all instances of the Point class will now have the same methods as EditablePoint instances do (the !(memberName in pointProto) clause ensures we don't overwrite methods that were already defined).
However, as you yourself have said, this is a massive antipattern, since at this point you're blurring the line between the two classes to a point where it's unclear why they even exist as separate classes; seeing an array of Points being used as if they were EditablePoints is a 100% sure way to confuse anyone who's reading your code. If you think you need something like this, it's probably better to rethink your model structure.
You could use Object.assign to add properties and setPrototypeOf to add methods by setting prototype of EditablePoint to prototype of Point, but since EditablePoint inherits from Point you will still have Point methods.
class Point {
constructor() {
this.point = true;
}
foo() {
console.log('foo')
}
}
class EditablePoint extends Point {
constructor() {
super();
this.editable = true;
}
bar() {
console.log('bar')
}
}
function edit(editablePoint) {
Object.assign(editablePoint, new EditablePoint);
Object.setPrototypeOf(editablePoint, EditablePoint.prototype)
}
let point = new Point();
edit(point);
console.log(point)
point.foo();
point.bar();
I think you just should not make an EditablePoint class that inherits from Point and has all the methods necessary for editing. Instead, use composition over inheritance, and make a class
class PointEditor {
constructor(point) {
this.point = point;
…
}
…
}
with all the methods necessary for editing the point associated to the editor. This way, you can instantiate as few / as many PointEditors for the points in your array that you want to edit, without changing anything about how the points in the array are created.

Is WeakMap performance influenced by the Key Object?

When using a WeakMap Object in JavaScript, the get and set methods require a Key to be passed in as argument.
FYI, I'm using WeakMap to simulate private properties for a class, something like this:
window.MyObject = (function(){
let _privateProperty = new WeakMap();
class MyObject {
constructor(value) {
_privateVariable.set(this, value);
}
SamplePrivateGet() {
return _privateVariable.get(this);
}
}
return MyObject;
})();
My question is: is get and set performance influenced by the object used as key?
I know primitive types can't be used as key, but maybe using an object with only one property as key could be faster than using an object that has, say, one thousand properties.
I tried looking at ECMA specifications but it's not specified, my guess is it's because it would depends on the browser's implementation.
EDIT: There are two ways to approach this with WeakMaps, one is declaring a single _privateProperties WeakMap to which I will add my private properties, the other declare one WeakMap for each of the private properties. I'm currently using the second approach so that every WeakMap can be Garbage Collected separately, but maybe going with the first would allow me to make much less .get calls. But I guess that's out of the scope of the question :)
Not sure if this is good idea, but you could make kind of private instance properties by using Symbols.
const PRIVATE_VAR = Symbol('MY_CLASS::PRIVATE_VAR');
const PRIVATE_FUNC = Symbol('MY_CLASS::PRIVATE_FUNC');
export default class MyClass{
constructor(value){
this[PRIVATE_VAR] = value;
}
[PRIVATE_FUNC](){
/* ... */
}
}
Without access to those Symbols values, it is quite hard to access those specific instance properties (plus instance properties defined with Symbols are non-enumerable in classes and objects).
btw 'Private instance methods and accessors' are at stage 3 so such solutions might not be relevant soon
https://github.com/tc39/proposals

Get constructor name of object

How can I get name of object's class? I mean "Process" in this example
I see two ways to get it. First one is to write a getter in this class like
getClassName(){return "Process"}
But I suppose it will be an error if I try to call this method in object which doesn't belong to this class and hasn't got method like this.
And second one is using object instanceof Process. But maybe there is some way to make it better and more correctly?
You can get it from name on constructor:
console.log(object.constructor.name);
When you do ex = new Example, for instance, in the normal course of things that makes Example.prototype the prototype of the object that was created (ex), and the object inherits a constructor property from that object that refers back to the constructor (Example).
I say "in the normal course of things" because there are various ways those normal relationships can be changed. For instance, code could have overridden the constructor property with an own property on the object (ex.constructor = somethingElse;). To rule out that specific scenario, you could use:
console.log(Object.getPrototypeOf(object).constructor.name);
Live example:
class Example1 {
}
const e1 = new Example1();
console.log(e1.constructor.name); // "Example1"
class Example2 {
constructor() {
this.constructor = "I'm special";
}
}
const e2 = new Example2();
console.log(Object.getPrototypeOf(e2).constructor.name); // "Example2"
The TC39 committee members that specify JavaScript were happy enough to use the instance's constructor property in Promises when building the new promise that then and catch return (see Step 3 here which goes here and reads constructor from the instance) (and in some other places), so you wouldn't be out on your own if you also used it. They don't even go to the prototype of the instance.
But yes, just for completeness, even if you go to the prototype for it, it's still possible for that to lead you astray, since the prototype's constructor property can also be mucked with:
class Example {
}
Example.prototype.constructor = Object; // Why would anyone do this? People are weird.
const e = new Example();
console.log(Object.getPrototypeOf(e).constructor.name); // "Object"
It's also possible to redefine the name on a function:
class Example {
}
// Why would someone do this? People are weird.
Object.defineProperty(Example, "name", {
value: "flibberdeegibbit"
});
const e = new Example();
console.log(Object.getPrototypeOf(e).constructor.name); // "flibberdeegibbit"
So...caveat user.
Note that the function name property is new as of ES2015 (as is class syntax). If you're using class syntax via a transpiler, it may or may not set name correctly.
Generally object instanceof Process is desirable if it's known for sure that object originates from this class/function. There may be situations where this won't be so. The appearance of several Process can be caused by iframes, multiple package versions, etc.
There is name property that already exists in regular functions class constructors. A known pitfall is that it will be mangled in minified code, so it is generally useless in browser JS, and its use can be considered an antipattern. name cannot be reassigned (in some browsers), so a separate property is needed to identify the class.
The proper way is to avoid this problem
But I suppose it will be an error if I try to call this method in object which doesn't belong to this class and hasn't got method like this.
is to use a getter:
class Process {
get className() { return 'Process'; }
...
}
Or a property:
class Process {
...
}
Process.prototype.className = 'Process';
As a result, there may be several Process classes that have Process className identifier. This may be desirable or not. While instanceof associates class instance with one particular class.
Use .constructor.name on the object. Each object's constructor by default refers to his creation function, which has a name property. It returns the name of the function.
class SomeClass {
}
const obj = new SomeClass();
console.log(obj.constructor.name);
Use the name property as follows:
class Process {}
console.log(Process.name);
const process = new Process;
console.log(process.constructor.name);
This is the same way it works for normal prototypal inheritance using functions.

Categories

Resources