ECMAScript Private Fields

This proposal extends ECMAScript class syntax by introducing the following features necessary for supporting high-integrity classes:

A Brief Introduction

Private fields are represented as an identifer prefixed with the @ character. Private fields are lexically confined to their containing class and are not reified. In the following example, @x and @y are private fields whose type is guaranteed to be Number.

class Point {
 
    @x;
    @y;
 
    constructor(x = 0, y = 0) {
        this.@x = +x;
        this.@y = +y;
    }
 
    get x() { return this.@x }
    set x(value) { this.@x = +value }
 
    get y() { return this.@y }
    set y(value) { this.@y = +value }
 
    toString() { return `Point<${ this.@x },${ this.@y }>` }
 
}

An "at-name" can also appear as a primary expression, in which case this is used as the implied base value.

class Point {
 
    @x;
    @y;
 
    constructor(x = 0, y = 0) {
        @x = +x;
        @y = +y;
    }
 
    get x() { return @x }
    set x(value) { @x = +value }
 
    get y() { return @y }
    set y(value) { @y = +value }
 
    toString() { return `Point<${ @x },${ @y }>` }
 
}

In the above class, input values are converted to the Number type. If we wanted to throw an error when an invalid type is provided, we could use a nested function declared within the class body.

class Point {
 
    @x;
    @y;
 
    constructor(x = 0, y = 0) {
        @x = _number(x);
        @y = _number(y);
    }
 
    get x() { return @x }
    set x(value) { @x = _number(value) }
 
    get y() { return @y }
    set y(value) { @y = _number(value) }
 
    toString() { return `Point<${ @x },${ @y }>` }
 
    function _number(n) {
        // Throw if `n` is not a number or is NaN 
        if (+!== n)
            throw new TypeError("Not a number");
 
        return n;
    }
 
}

Because private fields are lexically scoped, declarations nested within the class body can access private state. (This example uses the proposed function bind operator.)

class Container {
 
    @count = 0;
 
    // Other fields... 
 
    clear() {
        if (this::_isEmpty())
            return;
 
        // Empty the container 
    }
 
    // Other methods... 
 
    function _isEmpty() {
        return @count === 0;
    }
}

As shown in the previous example, private fields may have an initializer. Private field initializers are evaluated when the constructor's this value is initialized.

For more complete examples, see:

Syntax

AtName ::
    @ IdentifierName
 
PrivateDeclaration[Yield] :
    AtName Initializer[?Yield](opt) ;
 
ClassElement[Yield] :
    PrivateDeclaration[?Yield]
    Declaration[?Yield]
    VariableStatement[?Yield]
    MethodDefinition[?Yield]
    static MethodDefinition[?Yield]
    ;
 
MemberExpression[Yield] :
    ...
    MemberExpression[?Yield] . AtName
 
CallExpression[Yield] :
    ...
    CallExpression[?Yield] . AtName
 
PrimaryExpression[Yield] :
    ...
    AtName

High-Level Semantics

Private Declarations

Initialization Model

Private References