Abstract

In this post I'll show how I managed to solve protected properties with ES6 that are on the other hand enumerable.

Intro

I've been using ES6 just recently and managed already to run into concrete walls when it comes to inheritance. The fact, that the parent constructor must be invoked, otherwise the child won't have access to the instance, is quite a tough one. In the previous version (ES5) creating the object, was basically a two step process, where the first step was to actually create a new Object, then call the constructor on that instance. According to this, the parent - child constructor call did not have any specific order.

ES5 implementation
  var Parent = function() {
    // acts as a constructor when called with new
    this.foo = 'bar';
}

var Child = function() {
    // this is already available
    this.childFoo = 'childBar';

    // call the "parent" constructor
    Parent.prototype.constructor.apply(this, arguments);
}

// Extend the prototype and assign the constructor back to the original one

ES6 implementation
  "use strict"

class Parent {
    constructor() {
        this.foo = 'bar';
    }
}

class Child extends Parent {
    constructor(...args) {
        // this not available yet
        super(...args);
        // this available from here
        this.childFoo = 'childBar';
    }
}

// No need to do anything with the prototypes "extends" has done everything already

Comparing the two, the ES6 implentation looks cleaner, which seems to be a fair trade for this heavy restriction. Let's now try and restrict that "childFoo" should only accept numbers. Obviously we should prevent direct access to the property and we should use getters and setters to apply the necessary restriction.

Making properties protected

In ES5 I'd add local variables inside the constructor and define the properties with Object.defineProperty/defineProperties.

  var Child = function(data) {
    var protectedFoo = 0;

    Object.defineProperty(
        this,
        'foo',
        {
            configurable: true,
            enumerable  : true,
            get         : function() {
                return protectedFoo;
            },
            set         : function(val) {
                // do whatever with the incoming value
                protectedFoo = val;
            }
        }
    );

    Parent.prototype.constructor.apply(this, arguments);
}

That is all good, since the parent will set all the data for the child object. Work done for ES5.

Let's check the same for ES6. There we immediately face the problem, that the parent will not see the child properties as those are not defined yet - since "this" is not available before the "super()" call.

So for ES6 another approach is required.

Thankfully the prototype of the object exists before any instance of the object exists. With ES5 we could declare the setters on the instance level, with ES6 it has to be done on the protoype. The question then, where to define the variable holding the protected property? For this I'll be using another ES6 feature, WeakMap.

  "use strict"

let protectedFoo = new WeakMap();

class Child extends Parent {
    // no real need to redefine the constructor for now

    get protectedFoo() {
        return protectedFoo.get(this);
    }

    set protectedFoo(val) {
        // do whatever with the incoming value
        protectedFoo.set(this, val);
    }
}

That wasn't that bad at all. Now let's compare the objects.

Both do have the same "protectedFoo" property that can only be accessed through a function where we can filter the incoming value. If we wanted, we can make the object immutable by not defining the setter. The big difference is that the ES6 instance will not have the properties on the object level, so they won't be enumerable. This might be a problem for example when we want to convert our object into a JSON.

The solution to have properties enumerable with ES6
  "use strict"

// let's use a Map for all the protected members
let protecteds = new Map([
    ['protectedFoo', new WeakMap()]
]);

class Child extends Parent {
    consructor(...args) {
        super(...args);

        Object.defineProperty(
            this,
            'protectedFoo',
            {
                enumerable  : true,
                configurable: true, // this is similar to "final" if set to false
                set         : this.__lookupSetter__('protectedFoo'),
                get         : this.__lookupGetter__('protectedFoo')
            });
    }

    get protectedFoo() {
        return protecteds.get('protectedFoo').get(this);
    }

    set protectedFoo(val) {
        // do whatever with the incoming value
        protecteds.get('protectedFoo').set(this, val);
    }
}

Now if we try to convert this into a JSON the JSON will no longer be empty.

This few lines are so compact that I'll just try to explain each moment.

  1. constrcutor is invoked
  2. parent constructor is called - magic happens and "this" will already exist in the parent constructor
  3. parent constructor is looking for setters and finds one in the "Child" prototype - the value is stored in the protecteds Map
  4. after the parent constructor is finished, the property gets defined on the instance
  5. the getter/setter will point to the function defined in the object's prototype

Crazy, huh?


1,648 4 16