DEV Community

Cover image for Classes in JS: Public, Private and Protected
Parwinder 👨🏻‍💻
Parwinder 👨🏻‍💻

Posted on

Classes in JS: Public, Private and Protected

Even though ES6 introduced the class keyword that fairly well mimics classes and allows us to jump into object-oriented programming, JavaScript is missing the ability to create public, private, and protected members in a class.

If you have worked with any object-oriented language, you must know the importance of internal vs external interface. Internal interface refers to the methods and properties of a class that is only accessible by the class itself and not from outside. In contrast, the external interface has methods and properties that are also accessible from outside the class.

The three major keywords at play are public, protected, and private.

  1. Public: These members of the class and available to everyone that can access the (owner) class instance.
  2. Private: These members are only accessible within the class that instantiated the object.
  3. Protected: This keyword allows a little more access than private members but a lot less than the public. A protected member is accessible within the class (similar to private) and any object that inherits from it. A protected value is shared across all layers of the prototype chain. It is not accessible by anybody else.

The protected keyword is the hardest keyword of the three to imitate in JavaScript.

Public

This is the default nature of JavaScript. If something has access to an object, it does have access to its members. Example:

const myObject = {
    name: "Parwinder",
    sayMyName: function () {
        return this.name;
    }
}

console.log(myObject.name); // Parwinder
console.log(myObject.sayMyName()); // Parwinder

In the above example, I can access the property and method without any issue. If you would rather prefer it in a class syntax:

class ObjectCreator {
    name;

    constructor(name) {
        this.name = name;
    }

    sayMyName() {
        return this.name;
    }
}

const myObject = new ObjectCreator("Parwinder");
console.log(myObject.name); // Parwinder
console.log(myObject.sayMyName()); // Parwinder

Private

There are multiple ways of creating private variables in JavaScript. First is closures.

function carMonitor() {
    var speed = 0;

    return {
        accelerate: function () {
            return speed++;
        }
    }
}

var car = new carMonitor();
var redCar = new carMonitor()
console.log(car.accelerate()); // 0
console.log(car.accelerate()); // 1
console.log(redCar.accelerate()); // 0
console.log(redCar.accelerate()); // 1
console.log(car.accelerate()); // 2
console.log(redCar.accelerate()); // 2
console.log(speed); // speed is not defined

car and redCar maintain their own private speed variables, and speed is not accessible outside. We are enforcing the consumer to use the methods defined on the function or class rather than accessing the properties directly (which they should not). This is how you would encapsulate your code.

The second way is by using the # notation.

class ObjectCreator {
    #meaningOfLife;

    constructor(name) {
        this.#meaningOfLife = 42;
    }

    returnMeaningOfLife() {
        return this.#meaningOfLife;
    }

    #returnAMessage() {
        return "You will do great things in life";
    }
}

const myObject = new ObjectCreator("Parwinder");
console.log(myObject.returnMeaningOfLife()); // 42
console.log(myObject["#meaningOfLife"]); // undefined
console.log(myObject.#meaningOfLife); // SyntaxError
console.log(myObject.#returnAMessage); // SyntaxError

The language enforces encapsulation. It is a syntax error to refer to # names from out of scope. Public and private fields do not conflict. We can have both private #meaningOfLife and public meaningOfLife fields in the same class.

🚨 The # method for declaring private members of a class is in part of ES2019/ES10.

Protected

Like I said at the beginning of this post, protected is the hardest of all 3 to implement in JavaScript. The only way that I can think of doing this is by using a class that has a getter for a property without a setter. The property will be read-only, and any object will inherit it from the class, but it will only be change-able from within the class itself.

🙏 If you have an example of creating protected members of the class (or as close to protected as we can get), please do share!

class NameGenerator {
    _name;

    constructor(name) {
        this._name = name;
    }

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

let nameGenerator = new NameGenerator("John");
console.log(`My name is ${nameGenerator.name}`); // My name is John
nameGenerator.name = "Jane"; // Cannot assign to 'name' because it is a read-only property.

Top comments (7)

Collapse
 
obscerno profile image
Obscerno • Edited

This is my crack at the problem. I think this works back to es3 but I'm not sure?

// Declare objects within an anonymous function to limit access.
var objectRefs = (function() {
  // This is the object we want to inherit from.
  function Base(param1, _protected) {
    var _public = this;
    var _protected = _protected || {};
    var _private = {};

    // Declare some variables
    _public.shared = "Anyone can access this!";
    _protected.inherited = "This is protected";
    _private.secretVar = "Children cannot access this.";

    // Let's try a few functions.
    _public.foo = function() {
      // We can access protected and private functions here. This would
      // not be possible if we attached it to the prototype.
      console.log(_protected.inherited);
      console.log(_private.secretVar);
      _private.secret();
    };

    _protected.bar = function() {
      // One thing to watch out for: private functions called after
      // construction do NOT have access to the object via 'this'. This is
      // masked by the fact that I assigned it to the '_public' var.
      // More reading: https://stackoverflow.com/q/20279484/3658757
      console.log(_public.shared);
    };

    _private.secret = function() {
      // The same warning in _protected.bar applies here too.
      console.log(_public.shared);
    };
  }

  // Inherits from Base
  function Derived(param1, _protected) {
    var _public = this;
    var _protected = _protected || {};
    var _private = {};

    // Inherit (ready for the magic?)
    Base.call(this, param1, _protected);

    // Since we passed a reference to the "_protected" object as an argument
    // to the Base object, it has been attaching all of its protected
    // variables to it. We can now access those variables here:

    // Outputs "This is protected"
    console.log(_protected.inherited);

    // We can also access protected functions:
    _protected.bar();

    // We can even override protected variables and functions.
    _protected.inherited = "New value!";

    // We cannot access private variables belonging to Base.
    // This fails:
    // console.log(_private.secretVar);
  }

  // We don't want to allow public access to the constructors, because this
  // would let outside code pass in a '_protected' var. Instead, we create new
  // objects that accept all params minus '_protected', and inherit from the
  // target object.
  return {
    Base: function(param1) {
      Base.call(this, param1);
    },
    Derived: function(param1) {
      Derived.call(this, param1);
    }
  };
}());

// Assign the constructors to variables for clarity.
var Base = objectRefs.Base;
var Derived = objectRefs.Derived;

// This is how you construct the object.
var newDerived = new Derived("param1");

// Public functions are accessible.
newDerived.foo();

// Protected functions are not. These fail:
// newDerived.bar();
// newDerived.protected.bar();
Enter fullscreen mode Exit fullscreen mode
Collapse
 
projektorius96 profile image
Lukas Gaucas

Seems very promising. Now examining the code! :O

Collapse
 
obscerno profile image
Obscerno • Edited

Have fun! I'd like to add that in practice I'd probably not go to these length to protect the variables unless it was absolutely necessary. I'd probably do something more like:

// This is the object we want to inherit from.
function Base(param1, _) {
    var _this = this;
    var shared = _ || {};

    // Declare class variables and functions like this:
    this.foo = "Anyone can access this!";

    shared.bar = function() {
        console.log(secret());
        return "Shared function accessed!";
    };

    var secret = function() {
        // _this is needed to access object in private functions, otherwise leave out.
        return _this.foo;
    };
}

// Inherits from Base
function Derived(param1) {
    // Inherit
    var shared = {};
    Base.call(this, param1, shared);

    // Outputs "Shared function accessed!"
    console.log(shared.bar());
}

// Exclude shared param when not inheriting.
var myBase = new Base("param1");
var myDerived = new Derived("param1");
Enter fullscreen mode Exit fullscreen mode

Yo use it the same, you just have to trust people not to pass in a _ argument on construction to snatch the shared variables. I think it's a step up from defining public variables like this._myProtectedVar, but it's not technically protected (which is why I call the variable shared inside the function).

Collapse
 
ashleybooniphone profile image
ashleybooniphone • Edited

I have managed to emulate an extended protected class (somewhat) using a function that contains ES6's magic to destructure an Object from another function and an Object.assign to extend the Object on return.

The code in question:

Collapse
 
trusktr profile image
Joe Pea

A bit late, but check out my package lowclass, and the unit tests that depict the possible ways to use it:

github.com/trusktr/lowclass

It has, among other things, a class implementation that creates a runtime abstraction of encapsulation, included protected and private.

This example shows protected works:

const Animal = Class('Animal', (Protected) => ({
  protected: {
    thisIsProtected() { return 42 }
  },
  thisIsPublic() {
    console.log(Protected(this).thisIsProtected())
  }
}))

const anim = new Animal()
anim.thisIsPublic() // logs 42
// not possible to access `thisIsProtected` here.

const Dog = Class('Dog').extends(Animal, (Protected) => ({
  aPublicMethod() {
    Protected(this).thisIsProtected() // call the protected method from Animal
  }
}))

const dog = new Dog()
dog.aPublicMethod() // logs 42
// not possible to access `thisIsProtected` here.
Enter fullscreen mode Exit fullscreen mode

Private works much the same way, but as you guessed it, usable only in the class where it is defined.

We have the new #private syntax in JavaScript classes now, but it has a fairly big downside in that it will inadvertently break code when an instance with private fields is passed to other code that wraps the object with Proxy (many frameworks today use Proxy):

lea.verou.me/2023/04/private-field...

Built-in protected is nowhere to be seen. The TC39 people working on class syntax don't like the concept of "protected" because "if you can extend from a class and access a protected member, then it is public, so we don't need protected".

Collapse
 
cronixheadache profile image
Cronix

Hello,
not sure how much my code is correct in terms of using private properties in the super class simulating the "protected behavior", but considering that protected properties are not natively supported, this is the trick I found and which works for my little project. Hope this can help.
in short, I declare the "protected" properties as private in the super class and in the derivated classes I can get/set the values using getter and setter from the super class.
Somehow, using these methods it works and each derivated class has its own values (which I can never access directly with "this" but I have always to go through the getters and setters).

Below the example code.

I'd like to have your opinion on that.
thanks guys and happy coding.


class Car {
    #model = "generic";
    #color = "rust";

    constructor(model, color) {
        this.#model = model || this.#model;
        this.#color = color || this.#color;
    }

    getModel() {
        return this.#model;
    }

    getColor() {
        return this.#color;
    }

    setModel(model) {
        this.#model = model;
    }

    setColor(color) {
        this.#color = color;
    }

    describeMe() {
        console.log("I am a " + this.#color + " " + this.#model);
    }

    changeModel(newModel, newColor) {
        this.setModel(newModel);
        this.setColor(newColor);
    }

}

class Ford extends Car {

    constructor() {
        super("ford", "black");
    }


    changeModel() {
        // here I want to force the model adding a string to previous model just to show how to get the value
        var myModel = this.getModel();
        super.setModel(myModel + " but more beautiful than previous model");
    }

}

class BMW extends Car {

    constructor() {
        super("BMW", "Silver");
    }

}

var fiesta = new Ford();
var bmw = new BMW();
var generic = new Car();
fiesta.describeMe();
bmw.describeMe();
generic.describeMe();
fiesta.changeModel();
bmw.changeModel("Porche", "Yellow");
generic.changeModel("Ferrari", "red");
fiesta.describeMe();
bmw.describeMe();
generic.describeMe();
Enter fullscreen mode Exit fullscreen mode
Collapse
 
shadowtime2000 profile image
shadowtime2000 • Edited

For protected variables you could also kind of use what was used for private variables with a function.

function NameGenerator(name) {
var publicData = {name};
var privateData = publicData;
Object.freeze(publicData);
return {...publicData, john() {return NameGenerator("John")}};
}

Though I don't have any good idea on how to actually change the values instead of creating a new object;