javascriptecmascript-6weakmap

Questions about WeakMap and private variables


In the book I am reading at the moment, it talks about how we can use WeakMap to enforce privacy with the example code below.

const Car = (function() {
  const carProps = new WeakMap();
  class Car {
    constructor(make, model) {
      this.make = make;
      this.model = model;
      this._userGears = ["P", "N", "R", "D"];
      carProps.set(this, { userGear: this._userGears[0] });
    }
    get userGear() {
      return carProps.get(this).userGear;
    }
    set userGear(value) {
      if (this._userGears.indexOf(value) < 0)
        throw new Error(`Invalid gear: ${value}`);
      carProps.get(this).userGear = value;
    }
    shift(gear) {
      this.userGear = gear;
    }
  }
  return Car;
})();

I can not understand how such code can truly make gear property private and won't allow access from outside.

it seems that by using

carProps.set(this, { userGear: this._userGears[0] });

we are hiding userGear and making it private so it can not be accessed.

However, when I use

const car1 = new Car("Toyota", "Prius");
console.log(car1);
console.log(car1.userGear);

it shows me the result of

Car {
  make: 'Toyota',
  model: 'Prius',
  _userGears: [ 'P', 'N', 'R', 'D' ] }
P

I am not sure why I could access userGear and got 'P' instead of 'undefined' here where it is suppose to be not accessible.

Probably I am doing something wrong or understood the concept incorrectly.

Can someone please help me to understand the WeakMap?


Solution

  • The getter and setter of userGear shown in the code is just there to show you how you could communicate between the (private) carProps inside the class and the outer scope. The point of the example is to show that the carProps variable cannot be accessed, except through the deliberately exposed userGear methods. If those methods didn't exist, then after setting the WeakMap in the constructor, the outside consumer of Car wouldn't be able to see or do anything with it, eg:

    const Car = (function() {
      const carProps = new WeakMap();
      class Car {
        constructor(make, model) {
          this.make = make;
          this.model = model;
          this._userGears = ["P", "N", "R", "D"];
          carProps.set(this, { userGear: this._userGears[0] });
        }
        shift(gear) {
          this.userGear = gear;
        }
      }
      return Car;
    })();
    
    const car = new Car('foo', 'bar');
    // at this point, at this level of scope,
    // there is no way for a user of "car" or "Car" to reference carProps
    console.log(car.userGear);

    For another example that might make more sense, say that the constructor chose a random number that the user of the class had to guess:

    const Game = (function() {
      const gameProps = new WeakMap();
      return class Game {
        constructor() {
          gameProps.set(this, { randomNum: Math.floor(Math.random() * 10) });
        }
        guess(num) {
          return gameProps.get(this).randomNum === num ? 'Win' : 'Lose';
        }
      }
    })();
    
    const game = new Game();
    // at this point, at this level of scope,
    // there is no way for a user of "Game" or "game" to reference gameProps
    // or to figure out the random number, without guessing multiple times
    console.log(
      game.guess(1),
      game.guess(2),
      game.guess(3),
      game.guess(4),
      game.guess(5)
    );

    With the above code, there's no way for the caller of Game to figure out Game's internal random number without calling (the deliberately exposed method) .guess a few times. (unless Math.random gets monkeypatched beforehand...)