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
.
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'));
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
Jquery Validate Plugin - How to Create a Simple Custom Rule
What's the Difference Between Event.Stoppropagation and Event.Preventdefault
Detect the Internet Connection Is Offline
How to Post Urlencoded Form Data With $Http Without Jquery
Surprised That Global Variable Has Undefined Value in JavaScript
Chrome: Timeouts/Interval Suspended in Background Tabs
Count the Number of Occurrences of a Character in a String in JavaScript
Take a Screenshot of a Webpage With JavaScript
Use of .Apply() With 'New' Operator. Is This Possible
How to Count String Occurrence in String
How Does This JavaScript/Jquery Syntax Work: (Function( Window, Undefined ) { })(Window)
How to Delete an Item from State Array
Check Whether User Has a Chrome Extension Installed
How to Set a Cookie for Another Domain
Catch Browser's "Zoom" Event in JavaScript
How to Make Jquery UI Tabs Scroll Horizontally If There Are Too Many Tabs