It appears to be common practice to inherit from the EventEmitter if you want your class to support events.
For example Google does it for Puppeteer, the WebSocket module does it, mongoose does it, ... just to name a few.
But is this really good practice? I mean sure it looks nice and clean, but from an OOP perspective it seems wrong. For example:
const EventEmitter = require('events')
class Rectangle extends EventEmitter {
constructor(x,y,w,h) {
super()
this.position = {x:x, y:y}
this.dimensions = {w:w, h:h}
}
setDimensions(w,h) {
this.dimensions = {w:w, h:h}
this.emit('dimensionsChanged')
}
}
would make it seem like Rectangle is an EventEmitter at its core even though the eventing functionality is secondary.
What if you decide that Rectangle now needs to inherit from a new class called Shape?
class Shape {
constructor(x,y) {
this.position = {x:x, y:y}
}
}
class Rectangle extends Shape {
constructor(x,y,w,h) {
super(x,y)
this.dimensions = {w:w, h:h}
}
}
Now you would have to make Shape inherit from the EventEmitter. Even if only one class inheriting from Shape actually needs eventing.
Wouldn't something like this make way more sense?
class Shape {
constructor(x,y) {
this.position = {x, y}
}
}
const EventEmitter = require('events')
class Rectangle extends Shape {
constructor(x,y,w,h) {
super(x,y)
this.dimensions = {w, h}
this.em = new EventEmitter()
}
setDimensions(w,h) {
this.dimensions = {w:w, h:h}
this.em.emit('dimensionsChanged')
}
}
const rectangle = new Rectangle(1,2,3,4)
rectangle.em.on('dimensionsChanged', ()=>{console.log('dimensions changed')})
rectangle.setDimensions(10,20)
Yes, it absolutely makes more sense.
Inheritance should be used to denote is a relationships: class Rectangle extends Shape is ok since Rectangle is a shape, but here the rectangle itself is not an EventEmitter.
Instead, we have an can relationship: Rectangle can emit an event, and that is exactly where you should favor composition over inheritance, which is what happens in your last code snippet.
We can only speculate as to why some famous libs don't do that - retro-compatibility, simplicity of APIs, or simply bad design.
Related
PaperJS has many static constructors (like Rectangle) on their base constructor functions such as Shape. I'd like to extend one of those static constructors like this,
class Rect extends paper.Shape.Rectangle {
constructor() {
super(...arguments);
}
customMethod() {}
}
const A = new Rect();
But what I get in variable A is an instance of class "Shape" which doesn't have the "customMethod".
What is a solution?
Paper.js don't seem to use standard ecmascript
classes; their class system is based on
straps.js.
You can see it in action
in the source code of Rectangle.js,
that is the class available as paper.Rectangle.
If you wanted to extend Rectangle, you could
use its method .extend, like this:
const Rect = paper.Rectangle.extend({
customMove(dx, dy){
this.x += dx;
this.y += dy;
}
});
const rect = new Rect(10, 10, 100, 100);
rect.customMove(30, 10);
As for Shape.Rectangle it can't
be properly extended, since it is just
a method of the class Shape - see
the source code.
You could extend Shape itself to add a new static
method:
const Shape1 = paper.Shape.extend({
statics:{
Rect(){
//... access to this.Rectangle
}
}
});
And in some cases, it is feasible to just add
a new method directly to Shape:
paper.Shape.Rect = function(){
//... access this.Rectangle, or paper.Shape.Rectangle)
}
I have a statement like this.
let Polygon = class{
constructor(){
//stuff
}
draw(){
//more stuff
}
}
I want to extend this class as such:
class Board extends Polygon{
constructor(){
//stuff
}
}
But I also want to store it as a variable like Polygon. Using this:
let Board = class extends Polygon{
}
does not work. How can I store it like I did with Polygon?
It works just fine (but keep reading):
let Polygon = class{
constructor(){
//stuff
}
draw(){
//more stuff
}
};
let Board = class extends Polygon {
};
console.log(typeof Board);
...but note that
let Board = class extends Polygon {
};
and
class Board extends Polygon {
}
do exactly the same thing. They both:
Create a mutable variable called Board in the current execution context using modern identifier scoping and semantics (e.g., with the Temporal Dead Zone, etc.) upon entry to the scope in which the above occurs. let, const, and class all use modern identifier semantics. (var uses the legacy scoping and semantics.)
Initialize that variable with the constructor created by the class constructor to Board, when that statement/declaration is reached in the step-by-step execution of the code.
(In contrast, const Board = class extends Polygon { }; makes the identifier binding immutable.)
I have the following class 'X' and I want to add some stuff to its prototype dynamically, like this (this works):
class X{
someUnrelatedFunction(){};
}
//adding some stuff at runtime, in real situation, the ['a','b','c'] aren't "known" in advance like here.
['a','b','c'].forEach(elem => {
X.prototype[elem] = () =>{
console.log('called with : ' + elem);
};
})
//tests:
x = new X();
x.c();//works, outputs 'called with : c';
But in the class declaration itself. I would like to do it, to make things a bit more readable, i.e. I would like the 'prototype' initialization to belong to the class itself.
Right now I'm doing this in a 'constructor', like here:
class X{
constructor(){
//we don't want to do that again:
if(typeof X.prototype.inited === 'undefined'){
X.prototype.inited = true;
console.log('-constructor: initing prototype');
['a','b','c'].forEach(elem => {
X.prototype[elem] = () =>{
console.log('called with : ' + elem);
};
})
}else{
console.log('-constructor: no need to re-init prototype');
}
}
}
x1 = new X();
x1.a();
x2 = new X();
x2.b();
fiddle: https://jsfiddle.net/nypL29f3/4/
But this seems tricky for me, plus inside the class I'm actually referencing it kinda "outside", i.e. I'm using "X". If I ever changed the class name, I will also need to change that part of code.
So the question is - how to do that in a more readable and right way?
FWIW, The "real" case scenario is that I'm playing with ES6 by creating useless scripts like this one:
https://jsfiddle.net/kpion/mqyrewnx/9/
Here my doubts were about line #78 //this was my question - that it seems I need to do it outside the class YaLog.
Edit: just BTW - if you came here with a similar question - all the below answers answer the question in a way, and all are worth reading. Thanks everyone! :)
This sort of class prototype modifications is never performed from inside a constructor - primarily because class constructor is responsible for managing current class instance, not all class instances. A constructor is supposed to declare new methods ('log','info', etc) on this class instance, this is less efficient than declaring methods on class prototype and may be desirable or not for further class inheritance.
This is what decorators are intended for. They provide a convenient syntax for extension or modification class constructor and prototype.
A decorator can modify existing class prototype:
function withLogger(Class) {
['log','info','error','warn','group','groupEnd'].forEach(func => {
Class.prototype[func] = function() {
return this.callConsoleMethod(func, ...arguments);
};
});
return Class;
}
Or return a new class that extends existing class in non-destructive way, this allows to refer original members with super when shadowing them in wrapper class.
Decorators have neat syntax in ECMAScript Next/TypeScript:
#withLogger
class YaLog {
...
}
Since a decorator is basically a helper function, it can be applied directly in ES6 in a bit less expressive manner:
const YaLog = withLogger(class YaLog {
...
});
I would like the 'prototype' initialization to belong to the class itself.
That is not possible. A class body can only contain method definitions, not arbitrary code.
If the number of methods was known and only their names are dynamic, you could use computed property names, but this doesn't seem to be the case in your example.
Since you are looking for a more readable way, just put the class and all assignments of static values or dynamically created methods in a separate module. (This might be an ES6 module, an IIFE module, or a simple block scope for visual separation).
Right now I'm doing this in the constructor
Don't. This is inefficient, error-prone and horribly unreadable.
You can use inheritance. Create new class Y and add it methods from your array. Then you can extend original class (or any other) with Y:
class Y {}
const methods = ['a', 'b', 'c']
methods.forEach(elem => {
Y.prototype[elem] = () => {
console.log('called with : ' + elem)
}
})
class X extends Y {
someUnrelatedFunction() {}
}
const x1 = new X();
x1.a();
I am not sure what your use case is, but you can create a helper function which extends your original class. For example this could also work:
function extendWithMethods(methodNames, generalHandler) {
class Y {}
methodNames.forEach(elem => {
Y.prototype[elem] = () => generalHandler(elem)
})
return Y
}
class X extends extendWithMethods(['a', 'b', 'c'], methodName => {
console.log('called with : ' + methodName)
}) {
someUnrelatedFunction() {}
}
const x1 = new X()
x1.a()
I'm in a weird situation that i need to instantiate a new Class with a string stored in a variable but even i'm sure the class name is correct i get an error that given class name is not a constructor
Here is a dummy code that doesn't work:
class Foo {
constructor(){
console.log('Foo!');
}
};
const foo = 'Foo';
const bar = new window[foo]();
console.log(bar);
This trow this error:
Uncaught TypeError: window[foo] is not a constructor
One possibility is to use eval.
class Foo {
constructor() {
console.log('Foo!');
}
};
const foo = 'Foo';
const bar = eval(`new ${foo}()`);
console.log(bar);
You will have to evaluate the safety of using eval() in your particular circumstances. If you know the origin of the string you are inserting into the code that you run eval() on or you can sanitize it first, then it may be safe.
I personally would prefer a lookup table. If you have a known number of classes that you want to map by string, then you can make your own lookup table and use that. This has the advantage of there can be no unintended consequences if the string has weird stuff in it:
class Foo {
constructor() {
console.log('Foo!');
}
};
class Goo {
constructor() {
console.log('Goo!');
}
};
// construct dict object that contains our mapping between strings and classes
const dict = new Map([
['Foo', Foo],
['Goo', Goo]
]);
// make a class from a string
const foo = 'Foo';
let bar = new(dict.get(foo))()
console.log(bar);
If you were really going to go this route, you may want to encapsulate it in a function and then add error handling if the string is not found in the dict.
This should be better than using the global or Window object as your lookup mechanism for a couple reasons:
If I recall, class definitions in ES6 are not automatically put on the global object like they would with other top level variable declarations (Javascript trying to avoid adding more junk on top of prior design mistakes).
So, if you're going to manually assign to a lookup object, you might as well use a different object and not pollute the global object. That's what the dict object is used for here.
Similar to #jfriend00 ...
const className = "Foo";
const dynamicConstructor = {};
dynamicConstructor[className] = class {
constructor() {
console.log('Foo!');
}
};
const fooInstance = new dynamicConstructor[className]();
console.log(fooInstance);
A sort of factory class constructor can also be used
const classFactory = (_className) => {
let dynamicConstructor = {};
dynamicConstructor[_className] = class {
constructor(_code) {
this.code = _code;
console.log(`${_className} initialised with code: ${_code}!`);
}
};
return dynamicConstructor[_className];
}
const MyClass = classFactory("Foo");
let fooInstance2 = new MyClass(123);
console.debug(fooInstance2);
There are good solutions but I think we need a little bit more of theory
This questions is the best example to use a Factory Pattern
Design patterns can make your code more flexible, more resilient to change and easier to maintain.
Teo, is possible to use Factory Patterns in JavaScript?
Yes, my young Padawan.
But, what is the Factory pattern?
The factory pattern is a creational design pattern, which means it deals with object creation. There are three theoretical types of factory patterns:
Simple factory
Factory method
Abstract factory
Simple Factory is an object which encapsulates the creation of another object. In ES6 it could be a constructor being instantiated by new in a function and then return the instance like this:
class Player {...}
const PlayerFactory = {
makePlayer: (type, level) => new Player(type, level),
}
In this example makePlayer returns the instance of the Player class.
Factory Method defines one method for creating a class instance, which is overridden by subclasses who decide what to return.
In ES6 it could be implemented extending classes because there are no interfaces and using a method to instantiate and object using new
class Dragon {...}
class Snake {...}
class Player {
fightMonster() {
const monster = this.makeMonster()
monster.attack()
}
}
class Warrior extends Player {
makeMonster() {
return new Dragon()
}
}
class Knight extends Player {
makeMonster() {
return new Snake()
}
}
In this example, Player class call makeMonster method then Warrior and Knight classes override makeMoster method to return either a Dragon or a Snake class instance.
Finally, the Abstract Factory provides an interface for creating families of related or dependent objects without specifying their concrete classes
The interpretation of families could be like a category or list of classes. In ES6 it can be an instance that encapsulates a group of individual factories with a common goal. It separates the details of implementation of a set of objects from their general usage.
class WinButton {
constructor(options) {
this.name = 'WinButton'
this.options = options
}
}
class LinuxButton {
constructor(v) {
this.name = 'LinuxButton'
this.options = options
}
}
// Mapping names and class definitions
const supportedOS = new Map([
['Windows', WinButton],
['Linux', LinuxButton]
])
// Use a Factory Abstract pattern
const classFactory = (c, v) => {
const maker = supportedOS.get(c)
return new(maker)(v)
}
// Factory a class from a string
const name = 'Windows'
const param = {enabled: true}
const btn = classFactory(name, param)
console.log({btn})
In this final example we can see that the function classFactory uses the Factory Abstract pattern because instantiate a class from a list of supported OS (Linux or Windows) by setting maker with the constructor of the desired class, in this case WinButton class.
I wrote some code:
class Base {
// Default value
myColor = 'blue';
constructor() {
console.log(this.myColor);
}
}
class Derived extends Base {
myColor = 'red';
}
// Prints "blue", expected "red"
const x = new Derived();
I was expecting my derived class field initializer to run before the base class constructor.
Instead, the derived class doesn't change the myColor property until after the base class constructor runs, so I observe the wrong values in the constructor.
Is this a bug? What's wrong? Why does this happen? What should I do instead?
Not a Bug
First up, this is not a bug in TypeScript, Babel, or your JS runtime.
Why It Has To Be This Way
The first follow-up you might have is "Why not do this correctly!?!?". Let's examine the specific case of TypeScript emit. The actual answer depends on what version of ECMAScript we're emitting class code for.
Downlevel emit: ES3/ES5
Let's examine the code emitted by TypeScript for ES3 or ES5. I've simplified + annotated this a bit for readability:
var Base = (function () {
function Base() {
// BASE CLASS PROPERTY INITIALIZERS
this.myColor = 'blue';
console.log(this.myColor);
}
return Base;
}());
var Derived = (function (_super) {
__extends(Derived, _super);
function Derived() {
// RUN THE BASE CLASS CTOR
_super();
// DERIVED CLASS PROPERTY INITIALIZERS
this.myColor = 'red';
// Code in the derived class ctor body would appear here
}
return Derived;
}(Base));
The base class emit is uncontroversially correct - the fields are initialized, then the constructor body runs. You certainly wouldn't want the opposite - initializing the fields before running the constructor body would mean you couldn't see the field values until after the constructor, which is not what anyone wants.
Is the derived class emit correct?
No, you should swap the order
Many people would argue that the derived class emit should look like this:
// DERIVED CLASS PROPERTY INITIALIZERS
this.myColor = 'red';
// RUN THE BASE CLASS CTOR
_super();
This is super wrong for any number of reasons:
It has no corresponding behavior in ES6 (see next section)
The value 'red' for myColor will be immediately overwritten by the base class value 'blue'
The derived class field initializer might invoke base class methods which depend on base class initializations.
On that last point, consider this code:
class Base {
thing = 'ok';
getThing() { return this.thing; }
}
class Derived extends Base {
something = this.getThing();
}
If the derived class initializers ran before the base class initializers, Derived#something would always be undefined, when clearly it should be 'ok'.
No, you should use a time machine
Many other people would argue that a nebulous something else should be done so that Base knows that Derived has a field initializer.
You can write example solutions that depend on knowing the entire universe of code to be run. But TypeScript / Babel / etc cannot guarantee that this exists. For example, Base can be in a separate file where we can't see its implementation.
Downlevel emit: ES6
If you didn't already know this, it's time to learn: classes are not a TypeScript feature. They're part of ES6 and have defined semantics. But ES6 classes don't support field initializers, so they get transformed to ES6-compatible code. It looks like this:
class Base {
constructor() {
// Default value
this.myColor = 'blue';
console.log(this.myColor);
}
}
class Derived extends Base {
constructor() {
super(...arguments);
this.myColor = 'red';
}
}
Instead of
super(...arguments);
this.myColor = 'red';
Should we have this?
this.myColor = 'red';
super(...arguments);
No, because it doesn't work. It's illegal to refer to this before invoking super in a derived class. It simply cannot work this way.
ES7+: Public Fields
The TC39 committee that controls JavaScript is investigating adding field initializers to a future version of the language.
You can read about it on GitHub or read the specific issue about initialization order.
OOP refresher: Virtual Behavior from Constructors
All OOP languages have a general guideline, some enforced explicitly, some implicitly by convention:
Do not call virtual methods from the constructor
Examples:
C# Virtual member call in a constructor
C++ Calling virtual functions inside constructors
Python Calling member functions from a constructor
Java Is it OK to call abstract method from constructor in Java?
In JavaScript, we have to expand this rule a little
Do not observe virtual behavior from the constructor
and
Class property initialization counts as virtual
Solutions
The standard solution is to transform the field initialization to a constructor parameter:
class Base {
myColor: string;
constructor(color: string = "blue") {
this.myColor = color;
console.log(this.myColor);
}
}
class Derived extends Base {
constructor() {
super("red");
}
}
// Prints "red" as expected
const x = new Derived();
You can also use an init pattern, though you need to be cautious to not observe virtual behavior from it and to not do things in the derived init method that require a complete initialization of the base class:
class Base {
myColor: string;
constructor() {
this.init();
console.log(this.myColor);
}
init() {
this.myColor = "blue";
}
}
class Derived extends Base {
init() {
super.init();
this.myColor = "red";
}
}
// Prints "red" as expected
const x = new Derived();
I would respectfully argue this is, in fact, a bug
By doing an unexpected thing, this is undesired behavior that breaks common class extension use cases. Here is the initialization order that would support your use case and that I would argue is better:
Base property initializers
Derived property initializers
Base constructor
Derived constructor
Problems / Solutions
- The typescript compiler currently emits property initializations in the constructor
The solution here is to separate the property initializations from the calling of the constructor functions. C# does this, although it inits base properties after derived properties, which is also counterintuitive. This could be accomplished by emitting helper classes so that the derived class can initialize the base class in an arbitrary order.
class _Base {
ctor() {
console.log('base ctor color: ', this.myColor);
}
initProps() {
this.myColor = 'blue';
}
}
class _Derived extends _Base {
constructor() {
super();
}
ctor() {
super.ctor();
console.log('derived ctor color: ', this.myColor);
}
initProps() {
super.initProps();
this.myColor = 'red';
}
}
class Base {
constructor() {
const _class = new _Base();
_class.initProps();
_class.ctor();
return _class;
}
}
class Derived {
constructor() {
const _class = new _Derived();
_class.initProps();
_class.ctor();
return _class;
}
}
// Prints:
// "base ctor color: red"
// "derived ctor color: red"
const d = new Derived();
- Won't the base constructor break because we're using derived class properties?
Any logic that breaks in the base constructor can be moved to a method that would be overridden in the derived class. Since derived methods are initialized before the base constructor is called, this would work correctly. Example:
class Base {
protected numThings = 5;
constructor() {
console.log('math result: ', this.doMath())
}
protected doMath() {
return 10/this.numThings;
}
}
class Derived extends Base {
// Overrides. Would cause divide by 0 in base if we weren't overriding doMath
protected numThings = 0;
protected doMath() {
return 100 + this.numThings;
}
}
// Should print "math result: 100"
const x = new Derived();