Thursday, March 5, 2015

What? Wait. Really? Oh no! (a post about ES6 classes and privacy)

ECMAScript 6 is here with lots of new features, one of them is the class keyword. Yes, we can now write classes in JavaScript. You knew that already, right? Happy, pappy? 

I guess that about half of the World population currently is doing an imaginary High Five, the other half is probably planning a revolution (or will they just write a function?). Class is a controversial thing in society.

I am not that thrilled about it, but classes are here and will be used. Heavily. JavaScript classes are however a bit different from the ones written in C# or Java. How do I write a property? Are the properties I add private or public? Most of the examples I've seen in blog posts and presentations look something like this:

class myClass {
     constructor(firstName) {
          this.firstName = firstName;
     }
}


Is "firstName" a private property? Nope. It is publicly accessible - everyone can read and change it. If you have ever used function constructors, the behavior will be familiar. Everything added to an object using the this. syntax will be public.


Is it obvious that classes will work the same way? I don't think it is. I think the word class itself implies that certain functionality should be present. Especially for programmers coming from C#, Java or any other object oriented language. In JavaScript, classes also can only contain methods. Private variables cannot be added in the class body in the same way we are used to in C#. This code will throw an error:

// will this work? (No.)
class myClass {
     let _firstName = ''; // illegal syntax
}

Almost private?

To make a property almost private, you could use the new Symbol data type to generate a property name and store it in a Module together with the class. A symbol is a unique value (like a GUID) and will (most likely) prevent properties from being overwritten by code that consumes it.

An example:

// myModule.js
let first_Name = Symbol();

// How about this, will it be private? (No.)
class myClass {
     constructor(firstName) {
          this[first_Name] = firstName;
     }

     get name() {
          return this[first_Name];
     }
}

export default myClass;


This won't make the property private, because all keys of an object (including symbols) can be accessed using the Reflect object or the getOwnPropertySymbols method of the current object.


Really private?

I have searched for and found the following solution to be good enough for creating private properties in JavaScript classes: using WeakMaps (also new in ES6). This data structure will keep track of your property names and keep them private (as long as you don't export them, of course). The data in a WeakMap is linked to the source object and will also be garbage collected when the source object is removed.

// myModule.js
const first_name = new WeakMap();

class myClass {
     constructor (firstName) {
          first_name.set(this, firstName);
     }

     get name() {
          return first_name.get(this);
     }
}

export default myClass;


By adding the current instance of the class and the actual property value to a WeakMap, we have the behavior of private variables. If this class is extended (inherited), the properties cannot be accessed directly, since the WeakMap only exists in the module. You can't have it all, I guess.

Is it worth it?

I don't know. Time will tell. Personally, I think I mostly will use simple Modules and export object literals. Or why not use a plain old Immediately Invoked Function Expression (including private variables and methods) instead of complicating things with classes?


Update:
@stevenlangbroek has shared a nice alternative solution. I think looks like a hybrid between a class and an Immediately Invoked Function Expression. Check it out! Private variables in ES6 classes

No comments: