Skip to content
Padlocked chain on a wire fence

Private Properties in Javascript: How ES-Next Is Making Life Better

Ersin takes a comprehensive look at the patterns for hiding internal state, and contemplates the future.

Javascript (ecmascript) is an extremely global and dynamic language.  It is possible to overwrite any globally defined variable, and you can easily replace the prototype of any object. For the audience who are working with strongly typed language, it can be surprising to see the patterns we use for hiding internal state. A proposal for supporting private fields on a class has been progressing since July 2017, and it will likely be included in the next version of the language.

To understand the proposal, let’s take a comprehensive look at the patterns for hiding internal state.

_topsecret pattern

The convention in javascript is to use a the “_” as a prefix on the field to denote a private field. The field will remain publicly accessible. We see this pattern used in many frameworks and libraries. Another common variation is to have one special field that contains all the private state. React does this using the aggressively named “__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED” field.

class Block {
constructor(dimensions) {
this._width = dimensions[0]
this._length = dimensions[1]
this._height = dimensions[2]
}
getWidth() {
return this._width
}
getLength() {
return this._length
}
getHeight() {
return this._height
}
getVolume() {
return this._width * this._height * this._length
}
getSurfaceArea() {
return (this._height * this._length * 2) + (this._width * this._length * 2) + (this._width * this._height * 2)
}
}

The _topsecret pattern is what you will most commonly see across, the corpus of javascript. Using this pattern today makes sense. It will make your life easy when upgrading to the new private field syntax, and embraces the open and dynamic nature which the language affords.

Using a Closure to Encapsulate Variables

You might see this pattern used in javascript from the 2000’s. The idea is to use function scope to encapsulate the internal state of objects. It provides a high level of privacy for the state. The state can’t be accessed by any function or object outside the scope of the function. However, you might not use this pattern because it does not allow any overriding or sharing of private state between classes of the same type.

module.exports = function (dimensions) {
const [width, length, height] = dimensions

this.getWidth = function() {
return width
}
this.getLength = function() {
return length
}
this.getHeight = function() {
return height
}
this.getVolume = function() {
return width * height * length
}
this.getSurfaceArea = function() {
return (height * length * 2) + (width * length * 2) + (width * height * 2)
}
}

Ecmascript 6 Classes

In this example, we use the new class syntax, but encapsulate our internal state using the constructor function. This approach smells a bit, as all the methods need to be defined dynamically in the constructor, which heavily resembles the approach we used in our original closure example.

class Block {
constructor(dimensions) {
const [width, length, height] = dimensions
this.getWidth = function() {
return width
}
this.getLength = function() {
return length
}
this.getHeight = function() {
return height
}

this.getVolume = function() {
return width * height * length
}

this.getSurfaceArea = function() {
return (height * length * 2) + (width * length * 2) + (width * height * 2)
}
}
}

module.exports = Block

Symbol

A symbol is a special type added in es6. The main purpose of a symbol is as a unique key for objects. To use this technique, we still need to use a closure to ensure that our Symbol instance needs to be hidden in function or module scope. Exposing it would allow people to use that symbol for accessing the private state of the object. This approach smells slightly better, because not all methods need to be attached to the object in the constructor, as in our es6 class example.

const height = Symbol()
const width = Symbol()
const length = Symbol()

class Block {
constructor(dimensions) {
this[width] = dimensions[0]
this[length] = dimensions[1]
this[height] = dimensions[2]
}
getWidth() {return this[width]}
getLength() {return this[length]}
getHeight() {return this[height]}
getVolume() {
return this[width] * this[height] * this[length]
}
getSurfaceArea() {
return (this[height] * this[length] * 2) + (this[width] * this[length] * 2) + (this[width] * this[height] * 2)
}
}

module.exports = Block

Weakmap

The Weakmap class is a way of associating an object with a value. We can leverage this for private state by associating our object instance “this”, with an object of private keys that relate to the instance. The approach here resembles the symbol technique in that not all methods need to be defined within the constructor.

module.exports = function(dims) {
const privateProps = new WeakMap()
class Block {
constructor(dimensions) {
const [width, length, height] = dimensions
privateProps.set(this, {
width,
length,
height
})
}

getWidth () {
return privateProps.get(this).width
}

getLength () {
return privateProps.get(this).length
}

getHeight () {
return privateProps.get(this).height
}

getVolume () {
return privateProps.get(this).width * privateProps.get(this).height * privateProps.get(this).length
}

getSurfaceArea () {
return (privateProps.get(this).height * privateProps.get(this).length * 2) + (privateProps.get(this).width * privateProps.get(this).length * 2) + (privateProps.get(this).width * privateProps.get(this).height * 2)
}
}
return new Block(dims)
}

A weakmap is a memory efficient way for storing private objects against an object instance. The Babel transpiler uses them for the implementation of private fields.

The future

Private fields are denoted by the # character as a prefix to the field name. This makes the usage of private variables look a lot like the _topsecret pattern.  Migrating your codebase should be significantly easier thanks to this.

class Block {
#height = 0
#width = 0
#length = 0

constructor(dimensions) {
this.#width = dimensions[0]
this.#length = dimensions[1]
this.#height = dimensions[2]
}
getWidth() {return this.#width}
getLength() {return this.#length}
getHeight() {return this.#height}
getVolume() {
return this.#width * this.#height * this.#length
}
getSurfaceArea() {
return (this.#height * this.#length * 2) + (this.#width * this.#length * 2) + (this.#width * this.#height * 2)
}
}

module.exports = Block

The proposal for class fields will likely be arriving in the next major release of ecmascript. You can use this today with Babel, by enabling this plugin. React devs using create-react-app have this plugin enabled, so you can start experimenting with it in your class and component definitions today.  The online Babel REPL can be used to experiment with the syntax, try it out.

Why was a # sigil used instead of a keyword approach as in Java-like languages? Because javascript is a dynamic language, the metadata associated with a property is much more simplistic. If a “private” keyword was used, the semantics of property access on the “this” object would need to change significantly. The result would be a more complicated and risky code change for ecmascript runtime developers. Furthermore, “this” is already a complicated concept in the language. A sigil based approach allows an obvious differentiation between public and private property access within object methods, which is desirable for code comprehension.

Summing It All Up

Programming language, like natural language, must change in order to survive. We see the adoption of es6 classes across many frameworks and libraries in the universe of javascript code. An addition of private field semantics will be beneficial to the frameworks and libraries, and is a natural progression of the syntax. The timing is right, with several years of the es6 class syntax being used.

Our recommendation is to consider using the private class field syntax on client side projects that are using Babel for transpilation.  You could use Babel with Node projects, but our recommendation is to wait for the LTS release which includes the spec.

For more detail on the proposal for class fields, check it out on github.

Media Suite
is now
MadeCurious.

All things change, and we change with them. But we're still here to help you build the right thing.

If you came looking for Media Suite, you've found us, we are now MadeCurious.

Media Suite MadeCurious.