-1

EDIT: IT DOES NOT WORK

Thanks @loganfsmyth

Based on this Private properties in JavaScript ES6 classes, using Symbol() seems to be the best way because:

  • Methods are on prototype
  • Clean syntax
  • Almost private

But only "almost" since we can use Object.getOwnPropertySymbols() to loop through the properties. Now I have fixed the method a little bit, using closure.

const Person = (function () {
  const myKey = {};
  const lock = Symbol();

  return class {
    constructor(name, age) {
      const data = { name, age };  // PRIVATE PROPERTIES GO INSIDE DATA
      this[lock] = key => key === myKey ? data : null; // Lock check
    }

    info() {
      const data = this[lock](myKey);  // Open lock for every method
      return `Name: ${data.name}, age: ${data.age}`;
    }
  }
})()

// Extending
const Student = (function () {
  const myKey = {};
  const lock = Symbol();

  return class extends Person {
    constructor(name, age, school) {
      super(name, age);
      const data = { school };  // PRIVATE PROPERTIES GO INSIDE DATA
      this[lock] = key => key === myKey ? data : null; // Lock check
    }

    info() {
      const data = this[lock](myKey);  // Open lock for every method
      return `${super.info()}, school: ${data.school}`;
    }
  }
})()

var ryan = new Student('Ryan Vu', 25, 'Seneca College');
var jane = new Student('Jane Vu', 29, 'Queen University');
console.log(ryan.info());
console.log(jane.info());
//Name: Ryan Vu, age: 25, school: Seneca College
//Name: Jane Vu, age: 29, school: Queen University

It does have some bad points such as you have to call the lock function for every methods, but overall if your goal is to have a completely private class, I think this is a good way, and it follows the rule of prototype chain also. I'm posting here because I'm not sure if what I think is correct, I'm not experienced in programming. Please correct me. Thank you.

Edit - Brief explanation: All methods get access to private data through a lock. Although the outside can see the lock, only the methods and the lock itself can see the key.

Ryan Vu
  • 35
  • 5
  • At this point I’d probably just wait and hope that the class fields proposal makes it into next year’s release: https://github.com/tc39/proposal-class-fields – Felix Kling Oct 16 '17 at 03:21
  • It's more obscure, but it still has the same issue that using a symbol normally would have. You can still easily get the `Symbol` off the class, then use that to lock and unlock like any other case. If you just want to encourage users not to use stuff, an underscore prefix already does that, and if you want to prevent malicious changes with true privacy, a WeakMap is really the only option e.g. https://stackoverflow.com/a/31551606/785065 and even that has edge cases. – loganfsmyth Oct 16 '17 at 03:30
  • @loganfsmyth You can get the **lock** but you cannot get the **key**, and without the **key** you won't have access to the private properties – Ryan Vu Oct 16 '17 at 03:49
  • Stop trying so hard; you don’t need this much protection from yourself. Stick an underscore in front of the private property name and put it on `this`. – Ry- Oct 16 '17 at 04:01
  • @Ryan As I said this is for people with main goal is private properties. Also I'm asking to check if there's any downside that I don't know, which may help me gain more experience. So sorry if it bothers people – Ryan Vu Oct 16 '17 at 04:06
  • @RyanVu: Yep, I’m saying underscores accomplish that goal. `this._name = name`. Indicates “this is not part of my interface”. – Ry- Oct 16 '17 at 04:08
  • @Ryan I thought it's just a convention and we still can access through `this._name`? – Ryan Vu Oct 16 '17 at 04:15
  • @RyanVu: Correct, but does it matter? Just don’t do that. – Ry- Oct 16 '17 at 04:16
  • @Ryan Why you are so strict? I saw a lot of question about the topic, and and just wanna ask if my way can achieve those people's goals. I'm not sure because I'm not experience. Can't I learn here... – Ryan Vu Oct 16 '17 at 04:19
  • @RyanVu: Strict about what? I’m giving advice on practical JavaScript. You don’t have to take it, but aiming for unbreakable privacy from your own code is a deep and not very useful rabbit hole. – Ry- Oct 16 '17 at 04:21
  • No, I'm so appreciated your responses, it's just the way you said. But you edited it, so nvm – Ryan Vu Oct 16 '17 at 04:24
  • You can still get at the data: https://jsbin.com/hemujigupa/1/edit?js,console I guess technically you could set that property non-configurable to solve that though. – loganfsmyth Oct 16 '17 at 04:53
  • Once you've done all this, I'm not sure why you'd want it vs just using the WeakMap approach though. You've essentially implemented most of a WeakMap polyfill in this code. – loganfsmyth Oct 16 '17 at 05:00
  • @loganfsmyth Thank you a lot, I never think of your code. I'veread about WeakMap, but maybe I misunderstood it. I'll take a look at it later. Thanks for correcting me – Ryan Vu Oct 16 '17 at 05:08
  • 1
    The questions you see about private properties are likely from people coming from other languages which support visibility. But that’s just a not thing in JavaScript (at least so far). And while coming up with clever ways to get close to it might be fun, it just makes the Codename unnecessarily complex (in the long run). – Felix Kling Oct 16 '17 at 05:54
  • For WeakMap you could do https://jsbin.com/hevecuzoye/1/edit?js,console – loganfsmyth Oct 16 '17 at 15:15

1 Answers1

0

This approach does not work because you can still update the lock function with your own version, and use that version to get at the key, e.g.

var person = new Person();
var lock = Object.getOwnPropertySymbols(person)[0];
var key = (function(){
  let key = null;
  let origLock = person[lock];
  person[lock] = function(k){ key = k; return origLock.apply(this, arguments); };
  person.info();
  return key;
})();

const data = person[lock](key);

What I think would work would be to make the property non-configurable, thus changing

this[lock] = key => key === myKey ? data : null;

to

Object.defineProperty(this, lock, {
    value: key => key === myKey ? data : null,
    configurable: false, // this is the default, here for clarity, but omittable.
});

This prevents anything from re-assigning the [lock] property.

That said, by the time you get this far, you've essentially implemented a WeakMap polyfill. You could just as well consider doing

const Person = (function () {
  // Pull these methods off the prototype so that malicious code can't
  // reassign them to get data about the private accesses.
  const { set, get } = WeakMap.prototype;
  const storage = new WeakMap();

  return class {
    constructor(name, age) {
      set.call(storage, this, { name, age });  // PRIVATE PROPERTIES GO INSIDE DATA
    }

    info() {
      const data = get.call(storage, this);  // Open lock for every method
      return `Name: ${data.name}, age: ${data.age}`;
    }
  }
})();


var person = new Person();

console.log(person);
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251