Es6 Class Variable Alternatives

ES6 class variable alternatives

2018 update:

There is now a stage 3 proposal - I am looking forward to make this answer obsolete in a few months.

In the meantime anyone using TypeScript or babel can use the syntax:

varName = value

Inside a class declaration/expression body and it will define a variable. Hopefully in a few months/weeks I'll be able to post an update.

Update: Chrome 74 now ships with this syntax working.


The notes in the ES wiki for the proposal in ES6 (maximally minimal classes) note:

There is (intentionally) no direct declarative way to define either prototype data properties (other than methods) class properties, or instance property

Class properties and prototype data properties need be created outside the declaration.

Properties specified in a class definition are assigned the same attributes as if they appeared in an object literal.

This means that what you're asking for was considered, and explicitly decided against.

but... why?

Good question. The good people of TC39 want class declarations to declare and define the capabilities of a class. Not its members. An ES6 class declaration defines its contract for its user.

Remember, a class definition defines prototype methods - defining variables on the prototype is generally not something you do.
You can, of course use:

constructor(){
this.foo = bar
}

In the constructor like you suggested. Also see the summary of the consensus.

ES7 and beyond

A new proposal for ES7 is being worked on that allows more concise instance variables through class declarations and expressions - https://esdiscuss.org/topic/es7-property-initializers

variable and function declaration in classes in ES6

In javascript there is a huge difference between variables and properties. Variables get declared using var, let, const, then they you can assign values to them using the assignment operator (=). Variables are part of the current scope (everything between { and }), they are not related to objects. Properties are part of an object. They dont get declared, they exist after a value gets assigned to them until they are deleted. So actually you don't need to initialize them. However, this can lead to some very funny behaviour:

class Counter {
increase(){
this.count += 1;
}
}

const count = new Counter;
count.increase()
console.log(count.count); //NaN

Therefore it is a good practise to initialize them inside of the constructor:

class Counter {
constructor(){
this.count = 0;
}
increase(){
this.count += 1;
}
}

To beautify this and make it more familar for developers from other languages, there is a proposal (it might be a feature in the future) for class properties:

class Counter {

count = 0;

increase(){
this.count += 1;
}
}

however thats just syntactic sugar for the code above.

Access to a ES6 / ES7 static class variable within class and constructor

Static methods and properties are accessible through classes, not through this keyword:

class AddOrSelectAddress {

static allCountries = {

AD: "Andorra",

AE: "Vereinigte Arabische Emirate",

AF: "Afghanistan",

// ...

};

constructor() {

console.log('new');

console.log(AddOrSelectAddress.allCountries);

}

}

const myInstance = new AddOrSelectAddress();

How does ES6 class instance variable work

Both examples just don't work, the reason is that componentDidMount lifecycle fires after render lifecycle.

Because you don't cause re-render (with setState) in any phase, nothing will cause an additional render to read the updated value of result.

Sample Image

Therefore, the next example will always render No Result

import ReactDOM from 'react-dom';
import React, { Component } from 'react';

let result = null;

class App extends Component {
thisResult = null;
setResult = () => {
result = 10;
this.thisResult = 10;
};

componentDidMount() {
this.setResult();
}

render = () => {
return <div>{result || this.thisResult ? 'Result' : 'No Result'}</div>;
};
}

ReactDOM.render(<App />, document.getElementById('root'));

Edit flamboyant-snowflake-u4s6x

Variable not defined inside ES6 class without 'let' or 'var'

When I use the ES6 class syntax, a variable inside a method declared without 'let' or 'var' is undefined.

Actually, it's undeclared. That's why you get an exception.

However, when using the regular object syntax, it is defined.

That's because ES6 class methods automatically run in strict mode, your regular object syntax does only run in sloppy mode. (It should not! "use strict" mode everywhere!)

Assigning to an undeclared identifier will implicitly create a global variable, a thing which you'll definitely want to avoid.

Not using the class syntax solves this problem

No, the problem is solved by properly declaring the variable.

Member variables in ES6 classes

ES6 will almost certainly not cover syntax for defining class variables. Only methods and getters/setters can be defined using the class syntax. This means you'll still have to go the MyClass.classVariable = 42; route for class variables.

If you just want to initialize a class with some defaults, there is a rich new syntax set for function argument and destructuring defaults you can use. To give a simple example:

class Foo {
constructor(foo = 123) {
this.foo = foo;
}
}

new Foo().foo == 123
new Foo(42).foo == 42

Mocha finds global variable is undefined when extended by class

Static imports are always evaluated first, so the order of operations is roughly:

import { Queue } from 'queue.js'
class Queue extends cast.framework.QueueBase { // ReferenceError outside Chromecast!
initialize(){
// build queue here
}
}
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};

You can see that the mock is created after the reference to cast in app.js.

The only reliable way to run the mock creation before importing the app module is using a dynamic import:

// app.test.js
it('should do some stuff', async function () {
// inject a mock cast object
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
const { app } = await import('app.js');
await app();
// Make some assertions
delete global.cast;
});

If you prefer not to repeat the mock creation and the import in every test, you can move both out of the test definition:

// app.test.js
// inject a mock cast object
global.cast = {
framework: {
QueueBase: class {},
CastReceiverContext: {
getInstance: () => {},
},
},
};
const { app } = await import('app.js');
it('should do some stuff', async function () {
await app();
// Make some assertions
});
// Optionally clean up the mock after all tests
after(() => delete global.cast);

An alternative way to achieve data privacy in ES6 Class

A large majority of developers are aware of a leading underscore being a private variable and not for general API usage. So it being visible or not shouldn't really matter. Where as having getters and setters for every single property can really be too much boilerplate and frankly a waste of time.

Furthermore, this.setName and this.getName are not attached to the prototype chain which removes the ability for pretty much any optimization to occur - such as being able to reuse class methods between multiple instances.

If you want true privacy, use a factory function rather than a class.

And in response to your question, no it generally isn't good practice to write out classes that way.



Related Topics



Leave a reply



Submit