Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access Modifiers (Private, Protected) in ES6

Note: I already went through the below SO Question and 7 Answers (as of now) about Symbols, WeekMaps and Maps, Please read the full question before you vote: Private properties in JavaScript ES6 classes
Article: https://esdiscuss.org/topic/es7-property-initializers

Below is my Simple Class which contains Private, Public and Protected Properties and Methods.

  'use strict';
  class MyClass {
    constructor () {
      this.publicVar = 'This is Public Variable';
      this.privateVar = 'This is Private Variable';
      this.protectedVar = 'This is Protected Variable';
    } // Public Constructor Method.
    
    publicMethod () {
      console.log('   Accessing this.publicVar: ', this.publicVar);
      console.log('   Accessing this.privateVar: ', this.privateVar);
      console.log('   Accessing this.protectedVar: ', this.protectedVar);
      return 'Its Public Method'
    } // Public Method.

    privateMethod () {return 'Its Private Method'} // Private Method.
    protectedMethod () {return 'Its Protected Method'} // Protected Method.

    foo () {
      this.publicMethod();
      this.privateMethod();
      this.protectedMethod();
    } // Public Method
  } // end class

I'm instantiating the Object and calling the public method which is working as expected.

let MyObject = new MyClass;
MyObject.foo(); // Works fine.
console.log( MyObject.publicVar ); // Works
console.log( MyObject.publicMethod() ); // Works

Working as expected.

Now my question. I'm aware few things like Symbol are in the ES6 specification, what is the current workaround to get protected and private variables/methods working on ES6 classes.

console.log( MyObject.privateVar ); // Works
console.log( MyObject.privateMethod() ); // Works

I want this property and method to be visible only in its own class.

console.log( MyObject.protectedVar ); // Works
console.log( MyObject.protectedMethod() ); // Works

I want this property and method to be visible in its own class and inside classes extending it.

Workaround / better solution to achieve this behavior is appreciated

like image 763
Alamelu Venkat Avatar asked Dec 29 '15 19:12

Alamelu Venkat


2 Answers

Private properties

In ES6 (and before), all private property implementations rely on closure.

People have been doing it even before JavaScript has versions. WeakMap is just a variation that removes the need of new scope and new functions for each new object, at cost of access speed.

Symbol is a ES6 variation that hides the attribute from common operations, such as simple property access or for in.

var MyClass;
( () => {
  // Define a scoped symbol for private property A.
  const PropA = Symbol( 'A' );
  // Define the class once we have all symbols
  MyClass = class {
    someFunction () {
      return "I can read " + this[ PropA ]; // Access private property
    }
  }
  MyClass.prototype[ PropA ] = 'Private property or method';
})();

// function in the closure can access the private property.
var myObject = new MyClass();
alert( myObject.someFunction() );

// But we cannot "recreate" the Symbol externally.
alert( myObject[ Symbol( 'A' ) ] ); // undefined

// However if someone *really* must access it...
var symbols = Object.getOwnPropertySymbols( myObject.__proto__ );
alert( myObject[ symbols[ 0 ] ] );

As seen above, it can be worked around by Object.getOwnPropertySymbols(). Despite its existence, I always choice symbol over WeakMap. The code is cleaner, simpler, less gc work, and (I think) more efficient.

I personally avoid class, too. Object.create is much simpler. But that is out of scope.


Protected properties

Protected properties, by its nature, requires executing function to know the object of the calling code, to judge whether it should be granted access.

This is impossible in JS, not because ES6 has no real class, but because caller context is simply unavailable.

Due to various special natures of JavaScript, for the foreseeable future protected properties shall remain impossible.

[ Update ] Three years later, thanks to widespread support of module, it is possible to emulate most benefits of protected properties, see the answer below by Twifty. They are still public, but you need to go extra to access them, which means it is difficult to accidentally access or override them. [ /Update ]

Alternatively...


Package properties

Some languages have semi-protected properties, sometimes called "package private", where the method / property is accessible to members in the same module / package.

ES6 can implement it with closure. It is exactly the same as the private property code above - just share the scope and its symbols with multiple prototypes.

But this is impractical, since this requires that the whole module be defined under same closed scope, i.e. in a single file. But it is an option nonetheless.

like image 127
Sheepy Avatar answered Sep 20 '22 14:09

Sheepy


I'm late to answer this, but it is possible to emulate private AND protected methods in javascript.

Private methods/properties

Uses the well known Symbol approach

const someMethod = Symbol()
const someProperty = Symbol()

export default class Parent {
  constructor () {
    this[someProperty] = 'and a private property'
  }

  [someMethod] () {
    console.log('this is a private method')
    console.log(this[someProperty])
  }

  callPrivateMethod () {
    this[someMethod]()
  }
}

Protected methods/properties

By their nature, protected members are visible to derived classes. They must also mimic the super.method pattern.

symbols.js

export default {
   protectedMethod: Symbol()
}

parent.js

import symbols from './symbols'

const someMethod = Symbol()
const someProperty = Symbol()

export default class Parent {
  constructor () {
    this[someProperty] = 'and a private property'
  }

  [someMethod] () {
    console.log('this is a private method')
    console.log(this[someProperty])
  }

  [symbols.protectedMethod] () {
    console.log('I am the parent')
  }

  callPrivateMethod () {
    this[someMethod]()
  }
}

child.js

import Parent from './parent'
import symbols from './symbols'

export default class Child {
  [symbols.protectedMethod] () {
    console.log('I am the child')
    super[symbols.protectedMethod]()
  }

  callProtectedMethod () {
    this[symbols.protectedMethod]()
  }
}
like image 43
Twifty Avatar answered Sep 18 '22 14:09

Twifty