This is example of very basic code:
"use strict";
class aClass {
readFromA() {
console.log(this.a);
}
constructor() {
this.a = 5;
}
}
class bClass extends aClass {
readFromB() {
console.log(this.a);
}
constructor() {
super();
this.a = 10;
}
}
let bc = new bClass();
bc.readFromA(); //10
bc.readFromB(); //10
My intention is to involve the most modern techniques of object programming in JS. ES6 introduces classes and inheritance of them. It seems to be useless programming style yet. For example, code above overrides property "a" in class aClass by the same variable name in bClass. .
Lets assume that 2 proggramers create those classes. Each of them doesn't know what variable names will be used. If they both use the same variable name - it will couse a catastrophy! Both classes will read and write the same property making application crash. How to protect properties in classes against overriding and be able to utilize "extends" functionality?
Each of them doesn't know what variable names will be used. If they both use the same variable name - it will cause a catastrophy!
This is of course a very bad practice. You should not inherit from classes that you don't know, and every class should document its public members for exactly this purpose.
How to protect properties in classes against overriding and be able to utilize "extends" functionality?
Don't use the same keys. If you cannot ensure this using proper documentation or naming conventions, symbols were made to solve exactly this problem.
const a = Symbol("a");
export default class {
constructor() {
this[a] = 5;
}
readFromA() {
console.log(this[a]);
}
}
import ClassA from '…';
const a = Symbol("a"); // a different symbol than that in the AClass module
class BClass extends AClass {
constructor() {
super();
this.a = 10;
this[a] = 15;
}
readFromB() {
console.log(this.a, this[a]);
}
}
const x = new BClass();
x.readFromA(); // 5
x.readFromB(); // 10, 15
In the meantime I found solution to mentioned problem of private properties in classes. Bergi's idea was implemented. Here is the code:
"use strict";
let aClass = (function () {
let a = Symbol("a");
class aClass {
readFromA() {
console.log(this[a], this.b);
}
constructor() {
this[a] = "aaa"; //this is private
this.b="bbb"; // this is public
}
}
return aClass;
})();
let bClass = (function () {
let a = Symbol("a");
class bClass extends aClass {
readFromB() {
console.log(this[a], this.b);
}
constructor() {
super();
this[a] = "ccc"; //this is private
this.b="ddd"; // this is public
}
}
return bClass;
})();
let bc=new bClass();
bc.readFromA();
bc.readFromB();
The result is:
aaa ddd
ccc ddd
Thanks to encapsulation I managed with using the same property name "a" in both classes which receives different values for each of them. Property "b" is public and can be overriden. With this approach, we do not have to be careful using property names in both classes. There is no risk of accidentally overwriting the non public properties of the base class.
Moreover, this kind of encapsulation allows to use inheritance of classes in traditional way: by "extends" keyword.
Related
I am trying to figure out alternative ways to set a static (or class) property an ES6 Class and then change it after new instances of the class are created.
For example, lets say I have a class called Geo, and I need a static property called all that will give me the array of all instances of the Geo class.
This version works:
class Geo {
constructor(name){
this.name = name;
Geo.all.push(this);
}
}
Geo.all = [];
ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => 2
I would prefer to not set the property OUTSIDE of the class definition though. I've tried a few things but can't seem to create a static property within the class that I can update from the constructor.
I should also mention I need to be able to do this in the browser (Chrome) without use of Babel or similar.
Here are examples of some things I've tried:
class Geo {
constructor(name){
this.name = name;
Geo.all.push(this);
}
static get all() {
return [];
}
}
ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => 0
And another
class Geo {
constructor(name){
this.name = name;
Geo.all.push(this);
}
static all = [];
}
ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => error unexpected "="
There's no such thing as static all = [] in ES6. Class instance and static fields are currently stage 3 proposals which can be used via a transpiler, e.g. Babel. There's already existing implementation in TypeScript that may be incompatible with these proposals in some way, yet static all = [] is valid in TS and ES.Next.
Geo.all = [];
is valid and preferable way to do this in ES6. The alternative is getter/setter pair - or only a getter for read-only property:
class Geo {
static get all() {
if (!this._all)
this._all = [];
return this._all;
}
constructor() { ... }
}
Tracking instances in static property can't generally be considered a good pattern and will lead to uncontrollable memory consumption and leaks (as it was mentioned in comments).
This works for me for static properties.
class NeoGeo {
constructor() {
}
static get topScore () {
if (NeoGeo._topScore===undefined) {
NeoGeo._topScore = 0; // set default here
}
return NeoGeo._topScore;
}
static set topScore (value) {
NeoGeo._topScore = value;
}
}
And your example:
class NeoGeo {
constructor() {
NeoGeo.addInstance(this);
console.log("instance count:" + NeoGeo.all.length);
}
static get all () {
if (NeoGeo._all===undefined) {
NeoGeo._all = [];
}
return NeoGeo._all;
}
static set all (value) {
NeoGeo._all = value;
}
static addInstance(instance) {
// add only if not already added
if (NeoGeo.all.indexOf(instance)==-1) {
NeoGeo.all.push(instance);
}
}
}
Note: In the getter you could also check for the existence of the property using the in keyword or the hasOwnProperty keyword.
static get topScore () {
if (!("_topScore" in NeoGeo)) {
NeoGeo._topScore = 0; // set default here
}
return NeoGeo._topScore;
}
And using hasOwnProperty:
static get topScore () {
if (NeoGeo.hasOwnProperty("_topScore")==false) {
NeoGeo._topScore = 0; // set default here
}
return NeoGeo._topScore;
}
I recently had a similar issue of creating static classes.
I first tried it with constant class variables, but Chrome debugger threw an error.
So I defined the class variables 'static', also the getter methods.
Worked in Chrome.
class TestClass {
//static properties.
static _prop1 = [ 'A', 'B', 'C'];
static _prop2 = true;
static _prop3 = 'some String';
//constructor. Commented out because the class only has static elements.
//constructor () {}
//Getters.
static get prop1 () {
return this._prop1;
}
static get prop2 () {
return this._prop2;
}
static get prop3 () {
return this._prop3;
}
}
The only way to properly add a getter is to extend the class and use that extended class.
class Basic {
get firstGetter() {
return 'firstGetter'
}
}
class ExtendedClass extends Basic {
get firstGetter() {
return 'updatedFirstGetter'
}
}
}
Update your node to the version 12 or up and that's it ;)
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();
I try to extend a class with another, who has its constructor overrode, but when i instance this class, it doesn't have its own methods, but has its own properties.
Here's an example which doesn't work properly:
class A {
constructor () {
return {
pi: 3.14
}
}
}
class B extends A {
constructor () {
super();
this.c = 10;
}
d () {}
}
let b = new B();
console.log(b);
Here, b is :
Object {
c:10,
pi:3.14
}
So why the 'd' method is missing ?
EDIT:
Here is a concrete case:
I need to extend a class with HTMLElement, which i can instance and use like html element without registering with document.registerElement.
My code is:
class Element{
constructor(){
return document.createElement('div');
}
}
class Editor extends Element{
constructor(){
super();
}
}
and i want to use my class like this:
let editor = new Editor();
document.querySelector('body').appendChild(editor);
ECMAScript6 class methods are methods of an object's prototype object. You will find the method not in the object itself, but in obj.__proto__.
https://reinteractive.com/posts/235-es6-classes-and-javascript-prototypes
Here you can see and maybe understand what is going on internally with prototyped objects. And yes, the ES6 syntax is just a syntax change, not a new technology.
Here is the thing. I have a main class called A.
I want this class to extend class B.
class A extends B {}
But in fact, I want the class B to extend C, D or E on a specific condition:
class B extends B1 {}
or
class B extends B2 {}
or
class B extends B3 {}
So the B class would be a "fake" class, just to check a condition and then extend the right class.
In the final, the result would be the same as:
class A extends B1 {}
or
class A extends B2 {}
or
class A extends B3 {}
I know this is possible in PHP, with abstract classes or wrapper classes for example.
But how to do that in JavaScript ES6?
Thanks
Weird, but possible:
class subClassFirst {
report() {
console.log(`Extended ${this.name} from the first class`);
}
}
class subClassSecond {
report() {
console.log(`Extended ${this.name} from the second class`);
}
}
class subClassThird {
report() {
console.log(`Extended ${this.name} from the third class`);
}
}
function classCreator(condition) {
let sub;
switch (condition) {
case 'first':
sub = subClassFirst;
break;
case 'second':
sub = subClassSecond;
break;
case 'third':
sub = subClassThird;
break;
}
return (class extends sub {
constructor(name) {
super();
this.name = name;
}
});
}
let myClass;
myClass = classCreator('first');
let mcf = new myClass('f');
myClass = classCreator('second');
let mcs = new myClass('s');
myClass = classCreator('third');
let mct = new myClass('t');
mcf.report();
mcs.report();
mct.report();
I found blog post that gave an easy es6 way that doesn't use util.inherits
https://www.mikedoesweb.com/2017/dynamic-super-classes-extends-in-es6/
Here is how I used a passed option to determine which class to extend and then obfuscated that in the export
import ClassB from ' '
import ClassA from ' '
const ext = {
classA: ClassA, // the default
classB: ClassB
// can do as many as you want
}
function ExtendsMyClass (opts= {}) {
if (!new.target) {
throw new Error('Uncaught TypeError: Class constructor Interrupt cannot be invoked without \'new\'')
}
// one could vet opts here too including opts.extend
class MyClass extends ext[opts.extend || 'classA'] {
constructor(opts = {}) {
super(opts)
....
}
} // end MyClass
return new MyClass(opts)
} // end dynamic extend
export default ExtendsMyClass
export { ExtendsMyClass as MyClass }
I'll probably make this into "wrapper" utility function that accepts also the child class as well. That way one can dynamically extend any class as opposed to the one off implementation above. Could even implement dynamic imports if it was set up an async function.
So classes in javascript are really not setup in the same classical inheritance way as other languages, the best way to do what you want is to set the prototype of the object you are dealing with. There are a few ways.
Object.setPrototypeOf(currentObj, newPrototype);
Where newPrototype is the object you are wanting to inherit from. Here are a couple good articles on it if you want to learn the inner workings.
http://yehudakatz.com/2011/08/12/understanding-prototypes-in-javascript/
https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch5.md
There's a Node JS function for that
const util = require("util");
class MySubClass {}
class MySuperClass {}
util.inherits(MySubClass, MySuperClass);
In an ES6 class with some instance variables and methods, how can you add a mixin to it? I've given an example below, though I don't know if the syntax for the mixin object is correct.
class Test {
constructor() {
this.var1 = 'var1'
}
method1() {
console.log(this.var1)
}
test() {
this.method2()
}
}
var mixin = {
var2: 'var2',
method2: {
console.log(this.var2)
}
}
If I run (new Test()).test(), it will fail because there's no method2 on the class, as it's in the mixin, that's why I need to add the mixin variables and methods to the class.
I see there's a lodash mixin function https://lodash.com/docs/4.17.4#mixin, but I don't know how I could use it with ES6 classes. I'm fine with using lodash for the solution, or even plain JS with no libraries to provide the mixin functionality.
Javascript's object/property system is much more dynamic than most languages, so it's very easy to add functionality to an object. As functions are first-class objects, they can be added to an object in exactly the same way. Object.assign is the way to add the properties of one object to another object. (Its behaviour is in many ways comparable to _.mixin.)
Classes in Javascript are only syntactic sugar that makes adding a constructor/prototype pair easy and clear. The functionality hasn't changed from pre-ES6 code.
You can add the property to the prototype:
Object.assign(Test.prototype, mixin);
You could add it in the constructor to every object created:
constructor() {
this.var1 = 'var1';
Object.assign(this, mixin);
}
You could add it in the constructor based on a condition:
constructor() {
this.var1 = 'var1';
if (someCondition) {
Object.assign(this, mixin);
}
}
Or you could assign it to an object after it is created:
let test = new Test();
Object.assign(test, mixin);
In es6 you can do this without assigning and you can even invoke the mixin constructor at the correct time!
http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/#bettermixinsthroughclassexpressions
This pattern uses class expressions to create a new base class for every mixin.
let MyMixin = (superclass) => class extends superclass {
foo() {
console.log('foo from MyMixin');
}
};
class MyClass extends MyMixin(MyBaseClass) {
/* ... */
}
You should probably look at Object.assign(). Gotta look something like this:
Object.assign(Test.prototype, mixin);
This will make sure all methods and properties from mixin will be copied into Test constructor's prototype object.
I'm surprised to find that none of the answers mentions what I would consider a mixin in the sense of composition (and in contrast to inheritance), which to me is a function that adds functionality to an object. Here's an example making use of both inheritance and composition:
class Pet { constructor(name) { this.name = name } }
class Cat extends Pet { expression = 'miaow' }
class Dog extends Pet { expression = 'bark' }
class Human { constructor(name, age) { this.name = name; this.age = age; } }
class American extends Human { expression = 'say howdy' }
function canSayHello(...contexts) {
for (const context of contexts) {
context.sayHello = function() {
console.log(`Hello my name is ${this.name} and I ${this.expression}`)
}
}
}
canSayHello(Pet.prototype, Human.prototype); // apply the mixin
const garfield = new Cat('garfield');
const pluto = new Dog('pluto');
const joebiden = new American('Joe Biden', 79);
garfield.sayHello();
pluto.sayHello();
joebiden.sayHello();