tc39 / proposal-private-methods

Private methods and getter/setters for ES6 classes
https://arai-a.github.io/ecma262-compare/?pr=1668
345 stars 37 forks source link

This proposal does not address the actually existing needs for private fields #22

Closed zocky closed 6 years ago

zocky commented 6 years ago

I'm aware that much of this has been covered in the FAQ, but I still want to chime in:

The only good reason for this (IMO ugly, but that's a matter of taste) syntax seems to be the complications with what is accessible from "this" and the desire to give other objects in the same class access to the private properties.

But this idea that other objects of the same class should have access to the private fields of an object is what is very non-Javascript.

JavaScript objects don't really have classes, they have inheritable prototypes. All it takes for another object to become "of the same class" is to set its __proto__ property to some class's prototype. Which could be considered "dirty", but is also very common across libraries. All it takes to add a method to a "class" is to add a function member to the class's prototype. So protection by classes is ineffective and meaningless.

What Javascript programmers traditionally do is restrict access to data by scope, not by class. This is why there is a very common pattern that goes like this:

function myFactory () {
  var foo = "bar";
  var myNewObject = {
     doSomethingWithFoo() {
        // foo is accessible here, but nowhere outside the myFactory function
        doSomething(foo);
     }
  }
  return myNewObject;
}

This is what Javascript programmers call "private" variables. It is not the same thing as in Java or other programming languages. But it is very useful for our use cases , because it prevents programmatic access to these "private" variables and thus protects the information from other, potentially malicious scripts outside our control, in the same window (if we're talking about browsers).

The pattern is very tedious to write, and cannot be easily (or at all?) converted to using classes. A proposal that would solve this problem, and not the problem of making Javascript classes more like classes in other languages, would be preferable.

What we really need is this:

class myClass() {
  private foo = "bar";
  doSomethingWithFoo() {
     // foo is accessible here, but not accessible to methods of this or any other object
     // defined outside the class definition 
     doSomething(foo);
   }
} 
littledan commented 6 years ago

@zocky That does seem like a possible alternative, but the private fields repository attempts to explain the important use cases of class-private rather than instance-private (e.g., an equals method). I understand that it's not common practice today, but I'm wondering, what's an example of something that will break by allowing other instances of the same class to see the private fields or methods? How could you have malicious code in the same class body? Note that manipulating__proto__ doesn't give someone else access to private fields.

rdking commented 6 years ago

@littledan I get where you've been trying to go with the class-private names approach, though as someone who has written new languages, still think you've not thought thing through thoroughly (pardon the alliteration). An equals method can be developed even using private fields as described by @zocky while still taking into account most of what you're trying to achieve with private fields, and all without destroying the fact that class is just syntactic sugar. What if this example:

class myClass {
  private foo = "bar";
  doSomethingWithFoo() {
     // foo is accessible here, but not accessible to methods of this or any other object
     // defined outside the class definition 
     doSomething(this[foo]);
  }
  equals(other) {
    let retval = false;
    if (other instance of myClass)
      retval = this[foo] === other[foo];
    return retval;
  }
} 

translated to this:

var myClass = (function defineMyClass() {
  var privateScope = new WeakMap(); //Your private slots go here
  var privateNames = { //Your class-private names
    foo: Symbol() //From your intentions
  }
  with(privateNames) { //Ensures your class private names are available without this
    function _myClass() {
      privateScope.put(this, {
        [foo]: "bar"
      });
    }
    myClass.prototype.doSomethingWithFoo() {
      let pvt = privateScope(this);
      // foo is accessible here, but not accessible to methods of this or any other object
      // defined outside the class definition 
      doSomething(pvt[foo]);
    }
    equals(other) {
      let retval = false;
      if (other instanceof myClass) {
        let pvt = privateScope(this);
        let other_pvt = privateScope(other);
        retval = pvt[foo] === other_pvt[foo];
      }
      return retval;
    }
  }
  return _myClass; 
})();

I've spent much time trying to think of how the sigil (#) approach would work if implemented using current JavaScript. Frankly, it would probably look like the above. What's more, is that unless I missed something critical, what you're trying to do with class-private names can be in a fairly straight-forward manner be little more than a hidden implementation detail while allowing the syntax to remain something more palatable to the existing Javascript developer base.

rdking commented 6 years ago

@littledan Let's see how my suggestion compares with your FAQ.

The example and its expansion above should be in keeping with all the other points of your FAQ,, not withstanding the above comments. Put another way, you'd get the features you want without burdening us with ugly, somewhat confusing syntax, without changing the existing fact that class is syntactic sugar, and with significantly less workload for the engine developers.

littledan commented 6 years ago

We've discussed these proposals in other threads, and my conclusion is that we should not adopt them.

masaeedu commented 3 years ago

@littledan Is there a "primary" issue where discussion pertaining to other-instance-visibiilty was carried out/should be carried out?