Access Modifiers (Private, Protected) in ES6
Asked Answered
P

3

12

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

Pleuro answered 29/12, 2015 at 19:17 Comment(3)
If you already read about Symbols and WeakMap, what else do you need to know?Moreau
I have gone through those but expecting the example as a answer to restrict and extended class scope as wellPleuro
ES6 classes don't have private/protected intentionally: see here WeakMap/Symbols is more of a workaround to add them into ES6 classes, but ES6 classes are more of a way to define methods, than to replicate "classic" OO classes in languages such as Java and C#Metatarsus
C
17

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.

Colure answered 17/1, 2016 at 1:32 Comment(0)
S
7

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]()
  }
}
Stellastellar answered 20/3, 2018 at 12:35 Comment(6)
This is not protected, this is public, as everyone can access "symbols", or am I missing something?Pollinate
@BenjaminVanRyseghem To access the fields you will need to use the original Symbol created in parent.js, you cannot just create another symbol with the same name and expect it to also give access. This is emulated protection, as with every language there is always a way to bypass. The point of using symbols here is to prevent accidentally using the same field name on an object.Stellastellar
>To access the fields you will need to use the original Symbol created in parent.js you can also import symbols from './symbols'Pollinate
@BenjaminVanRyseghem, exactly my point. If your code intends to write to the protected/private fields then you MUST import the symbols. Let's say you publish a module 'string' with an intended private field called '_sum', any user can extend or add to the module a conflicting field named '_sum'. They would have to look into the source to find the bug. Using symbols prevents this. The private/protected members are safeguarded against accidental writes. This is not intended to hide fields from users, it is nothing more than a safety net. No language can hide these members.Stellastellar
someProperty and someMethod are present in global scope and using those, I can access private properties(according to you ) via this way parentObj[someProperty], so they are not private infactRancho
You can write to the "protected/private" fields without importing anything by retrieving symbols with Object.getOwnPropertySymbols(). Also, I think you forgot to add class Child extends ParentCome
I
0

Also can use a symbol as a "key" for enabling a version of a class with protected inheritance via gatekeepers and conditional behavior.

eg in this BST root is private and add/insert returns true. Did not want any direct node access.

However, an AVL subclass could use both, as well as some utility functions.

The key allows opinionated expressions of the class as either a standalone or a base. Could be done even DRY-er with decorators, or a protected access map (rather than pass through gatekeeper getters).

class BinarySearchTree {
  #root;

  constructor(sym) {
    this.#root = null;
    this.#access = sym;
  }

  #protectionCheck = () =>
    this.#access === PROTECTED ||
    (() => {
      throw new Error("Ah, ah, ah. You didn't say the magic word.");
    })();

  __root() {
    this.#protectionCheck();
    return this.#root;
  }

  add(val) {
    ...
    return this.#access === PROTECTED ? node : true;
  }
}

class AVL extends BinarySearchTree {
  constructor() {
    super(PROTECTED);
  }

  get #root() {
    return super.__root();
  }

  add = (value) => {
    const node = super.add(value);
    this.#balanceUpstream(node);
    return true;
  };
}
Ingold answered 21/12, 2021 at 8:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.