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.
Related
Let's say I want to create an array of Person using a random data library.
I could do something like
import {generateRandom} from 'someLib'
let people = []
function getPerson() ({
name: generateRandom.string()
age: generateRandom.number()
})
for (let i = 0; i < 10; i++) {
people.push(getPerson())
}
But I could also do something like
import {generateRandom} from 'someLib'
class Person {
constructor() {
this.name = generateRandom.string(),
this.age = generateRandom.number()
}
}
let people = []
for (let i = 0; i < 10; i++) {
people.push(new Person())
}
On a memory level, is there any difference in the outcome?
(This is just a theoretical question, I am not trying to solve any problem in particular)
I have found this question that is related to this Difference between creating a class in javascript to create an object and creating an class and object in Java
Which states that there are no classes in JS.
Is this just syntactic sugar? 2 ways of doing exactly the same thing?
If you want to examine the memory yourself, you can put this code in the console:
class Test{};
const test = new Test();
class Class {};
test.Class = Class;
test.classObj = new Class();
function func() {return {};};
test.func = func;
test.funcObj = func();
Take a heap snapshot with google chrome dev tools, sort by constructor and find the Test class.
You can then examine the memory of these functions and objects. Here's a screenshot of what I got:
You can see that the object instantiated by the class constructor is slightly larger than the one instantiated by the function. Expanding the prototype, you can see they both use the Object constructor, but the classObj has the additional Class constructor in its prototype chain.
You can also see that the class constructor appears to retain more memory than regular functions, retained size meaning memory that will be cleaned up by garbage collection if the function is no longer in use.
Seems to be an extra 172 bytes for the class constructor, and 24 bytes per object for an empty object.
Following VLAZ's comment, here's the results with 10 methods added, and 10 instances.
class Test{};
const test = new Test();
class Class {
method0(){};
method1(){};
method2(){};
method3(){};
method4(){};
method5(){};
method6(){};
method7(){};
method8(){};
method9(){};
};
test.Class = Class;
for (let i=0; i < 10; i++){
test["classObj" + i] = new Class();
}
function func0(){};
function func1(){};
function func2(){};
function func3(){};
function func4(){};
function func5(){};
function func6(){};
function func7(){};
function func8(){};
function func9(){};
function constructorFunc() {
return {
method0: func0,
method1: func1,
method2: func2,
method3: func3,
method4: func4,
method5: func5,
method6: func6,
method7: func7,
method8: func8,
method9: func9,
};
};
test.constructorFunc = constructorFunc;
for (let i=0; i < 10; i++){
test["funcObj" + i] = constructorFunc();
}
The shallow size of the class objects are now much smaller. This seems to be due to the fact that they can just store a reference to the class prototype rather than reference all of their methods directly.
At first glance, the retained size of Class seems to be smaller than constructorFunc, but expanding Class you can see a property named prototype which is an object retaining an extra 1.38 KB. Adding that to the 520 B of the class itself pushes it above the retained memory of constructorFunc. But the memory saved by creating instances of the class instead of an object will outweigh that pretty quick.
So seems like classes are the way to go.
Your first getPerson() is invalid syntax and the function does not even attempt to return anything (if it was valid syntax). So, people.push(getPerson()) would generate an array of undefined(if the syntaxError was fixed) which will be entirely different than your second code block which generates an array of objects.
If, what you meant to ask about was something like this:
let people = []
function getPerson() {
return {
name: generateRandom.string()
age: generateRandom.number()
}
}
for (let i = 0; i < 10; i++) {
people.push(getPerson())
}
Then, this and your class will each create an array of objects and those objects will each have your two properties on them. The Class will contain some additional setup on the prototype that allows for subclassing, but if you're just asking about using the object with two properties, then these two would fundamentally do the same thing.
On a memory level, is there any difference in the outcome?
Without methods, your two properties will occupy the same amount of memory. The two properties are assigned directly to the object so those two properties use the same memory.
The class will actually set up a few other things on the object like a .constructor property and the object will get its own separate prototype. The prototype is shared among all instances. So, there could be slightly more memory usage for the class instance, but it's unlikely this makes a material difference.
If you defined methods directly on the object in the first implementation and methods in the class in the second implementation, then the class would definitely be more efficient because there would be one copy of the methods on the prototype vs. many separate copies of the methods on each instance of the object. But, you didn't show that in your question.
Which states that there are no classes in JS. Is this just syntactic sugar? 2 ways of doing exactly the same thing?
Classes are syntactic sugar, but they make it easy to define things in an efficient way without having to do a bunch of manual things. And, because the language sets things up for you, then everyone using a class definition creates code that works the same way.
You can manually build the same object that instantiating an instance of a class does, but it's a lot more code to do write all the bookkeeping that a class does for you. Your simple object with just two instance properties doesn't show any of that or need any of that, but when you start having methods and sub-classing things and overriding base methods and then calling base methods in your derived implementation, the class definition takes care of a some bookkeeping for you and simply lets you write, good extensible code faster and with everyone doing it the same way. Again, your simple object with just two properties does not show or need what a class does, but other uses of objects do.
For example, the class definition and new Person() creates an object that automatically sets yourObject.constructor to be the constructor that created the object. This allows other code to know what type of object it is or allows other code to abstractly create new instances of the same object. Methods are put on the prototype of the object and made non-enumerable. Constructors can call super(...) to execute the base class constructor (whatever it happens to be). Without the class definition, you have to do that very manually by calling the exact right function in the base class.
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
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.
I'm digging deeper into JavaScript. I know it is possible to modify/enhance the behavior of JavaScript objects my modifying the prototypes.
For a special purpose I'd like to take Array and add all sorts of behavior to it. I TOTALLY get it is BAD to change all Arrays for my case. But it regular OO language, I could just extend it, add my functions, leave the original alone. Is there anything comparable in JS? Like, can I copy the Array prototype & put it onto another class & fiddle with that without affecting other arrays? Or any notions?
Not critical, but it would be helpful and certainly help me get a deeper understanding of JS which I am really getting into after years of Java, C++, etc.
You can extend the Array class and implement your extra functionality as methods on that class.
Instead of using let x = []; you now use let y = new myArray();.
class myArray extends Array {
constructor() {
super();
console.log(typeof this.push);
console.log(typeof this.shift);
}
logAndAdd(variable) {
this.push(variable);
console.log(variable);
}
}
let y = new myArray();
y.logAndAdd('hi');
I think you can simply use ES6 extends like you would in any other OO language.
Here is an example of a class that extends the native array and adds a log method. Only issue with this is that you will have to use the new keyword to create a new array.
class CustomArray extends Array {
log() {
console.log(this);
}
}
var customArrayInstance = new CustomArray();
customArrayInstance.push(1,2,3,4,5);
customArrayInstance.log();
// Creating an array using the array constructor
//
new CustomArray(200).fill(null).log()
What you could also do is use Symbols to extend the behavior of the native array in a non OOP kind of way. You will be basically extending the prototype of the native array in a non browser breaking way.
I think taking a look at Javascript Iterators and their possibilities is one option.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
I have used this approach in Typescript/Angular2 to achieve array-like collections, where I can define custom push/pop/pull operations in an OO like way.
Yes, you can do this, and in JavaScript we call this interface-ish thing a prototype.
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
};
Although personally, I would generally prefer to make "method" an independent function that takes and returns an array.
I've read through most of the Javascript inheritance references, but I'm afraid I'm still scratching my head as to how to do this.
I'm trying to put together several classes, in the general sense, that will have similar behavior and thought using prototypes might be a way to achieve that. So I create a base class as follows:
function my_base_class()
{
var a_common_object = undefined; // The true value of this can't be set until runtime.
// some other stuff ...
};
Ideally, I'd like a_common_object to be private or at least protected, but just getting it working would be a good first step. I then need to create several derived classes of which this might be one:
function my_derived_class()
{
this.do_something_to_common_object = function()
{
// Here I need to reference my_base_class.a_common_object but
// at this point there's no relationship between the two classes
};
};
I now set the prototype of my_derived_class while creating an instance:
my_derived_class.prototype = new my_base_class();
var my_derived_class_inst = new my_derived_class();
So at this point I'm hoping that I have an object - my_derived_class_inst which has traits of my_base_class including the static object a_common_object which I can access.
I have two questions:
How do I refer to a_common_object within my_derived_class when
there's no relationship established between the two classes?
How can I then change a_common_object to its true value, so that
all derived classes seamlessly pick up the new value.
Please don't simply refer me to the standard reference web sites on inheritance as I've read most of them through and I'm still no wiser. It seems to me that the answer should be really simple but so far it escapes me. Many thanks.
do_something_to_common_object() really doesn't have a way of reaching a_common_object directly.
a_common_object isn't a member of the instance created for the prototype. It's a local variable scoped inside the constructor. So, only a function that's also defined within the constructor can reach it (ref: closures):
function my_base_class()
{
var a_common_object = undefined;
Object.defineProperty(this, 'a_common_object', {
get: function () {
return a_common_object;
}
});
// ...
}
function my_derived_class()
{
this.do_something_to_common_object = function()
{
console.log(this.a_common_object); // uses getter to retrieve the value
};
};
It would still be publicly accessible, but your options are limited as JavaScript doesn't yet support or have an equivalent to access modifiers.
Though, with Object.defineProperty(), it would at least be read-only so far and non-enumerable by default (won't appear in a for..in loop).
At least until #2, where you'd need to also have a setter. Though, it would be a chance to validate the value being storing it.
Object.defineProperty(this, 'a_common_object', {
// ....
set: function (value) {
if (/* validator */) {
a_common_object = value;
}
}
});