New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why not use the "private" keyword, like Java or C#? #14

Closed
melanke opened this Issue Nov 2, 2015 · 715 comments

Comments

Projects
None yet
@melanke
Copy link

melanke commented Nov 2, 2015

No description provided.

@melanke melanke changed the title I woudl hate to use '@' instead of 'private', and why we need '@' all the time I woudl hate to use '@' instead of 'private', and why we need '@' all the time? Only in declaration of the variable would be enough, like Java or C# Nov 2, 2015

@melanke melanke changed the title I woudl hate to use '@' instead of 'private', and why we need '@' all the time? Only in declaration of the variable would be enough, like Java or C# I would hate to use '@' instead of 'private', and why we need '@' all the time? Only in declaration of the variable would be enough, like Java or C# Nov 2, 2015

@zenparsing

This comment has been minimized.

Copy link
Member

zenparsing commented Nov 2, 2015

Consider this scenario:

class X {
    private a;
    constructor(a) { this.a = a }
    swap(otherX) {
        let otherA = otherX.a;
        otherX.a = this.a;
        this.a = otherA;
        return otherA;
    }
}

Let's call swap with an instance of X as the argument:

let x1 = new X(1);
let x2 = new X(2);
x1.swap(x2); // --> 2

In this case, the reference to otherX.a should access the private field named "a", agreed?

What if we call swap with an object which is not an instance of X?

let x1 = new X(1);
let obj = { a: 3 };
x1.swap(obj); // --> TypeError or 3?

Without a type specifier on the otherX parameter, we don't have any way to tell the compiler whether otherX.a means:

  • Look up the "a" named property on otherX, or
  • Look up the private field named "a" on otherX.

We could just say that within the body of X, property lookups for "a" always refer to the private name, but that would result in surprising behavior in many cases. (Let's come back to this possibility later, though.)

That's why we need to syntactically distinguish between private field lookup and regular property lookup. There are a couple of different ways that we can distinguish them:

  • We could use a different lookup operator: @, for instance. this@x = 123
  • We could use a different kind of property name: @property, for instance. this.@x = 123.

I think the second option is the most natural. Unfortunately, either option uses up one of the two last remaining operator characters (the other being the ugly-duck #).

I hope that helps explain the rationale. I'll follow up with a comment exploring the other option that I mentioned above.

@zenparsing zenparsing changed the title I would hate to use '@' instead of 'private', and why we need '@' all the time? Only in declaration of the variable would be enough, like Java or C# Why not use the "private" keyword, like Java or C#? Nov 2, 2015

@zenparsing zenparsing closed this Nov 19, 2015

@yanickrochon

This comment has been minimized.

Copy link

yanickrochon commented Nov 19, 2015

I actually don't see anything wrong with the example you provided, even after reading a few times, there's nothing worng with using private (a reserved keyword already).

Consider the last snippet

let x1 = new X(1);
let obj = { a: 3 };
x1.swap(obj); // --> 3

In the code, since it's all being used inside the class and either the private a property of the object's a is accessed, there will be no access violation, therefore the code would not make any difference if a is private or not on the passed argument.

However, if such function would be given :

function swapA(a, b) {
  let tmp = a.a;
  a.a = b.a;
  b.a = tmp;
  return tmp;
}

Then a TypeError would be thrown if passing an instance of X as a.a would fail for a private property not being accessible.

You are not giving a convincing example to reject this proposal, you are just assuming that there should be a difference in the interpretation of this.a if a is declared as private.

The only valid argument to reject private a; is that it would confuse people into knowing what is private and what's not. Then again, since the property is private, it should not be enumerable anyhow, so would not be exposed externally. And we go full circle.

In the README, you are giving parsing directives, but do not describe how these properties are represented at run-time. Currently, properties are defined as enumerable (dfault true), configurable (default true), writable (default true), or with a getter and/or a setter. It should be fairly easy to assume that introducing property visibility would require adding more definable attributes, such as accessible (default true).

@zenparsing

This comment has been minimized.

Copy link
Member

zenparsing commented Nov 20, 2015

@yanickrochon If I understand your proposal correctly, you'd change normal property lookup to something like this:

Given obj.prop:

  1. Look up the "prop" named property in obj.
  2. If it exists and is a "private" property:
    1. Check the lexical scope of the current execution context to determine whether the access is allowed or not.
    2. If it's disallowed, then throw a TypeError.
  3. Return the property value, or undefined.

I believe this idea was considered early on and rejected, although the rationale probably isn't captured anywhere.

Think of what you might need to do to make that work.

  • You'd have to somehow encode a "private key" into each lexical environment.
  • You'd have to change the semantics of each and every property access (because any property access might result in a private field). Engines are highly optimized around the current property lookup semantics.
  • Would for-in enumerate over these properties?
  • Can someone shadow a private property with a normal property lower on the prototype chain? What about the reverse?
  • How do you prevent leaking the names of private fields to clients that shouldn't know that information? This is probably a fatal information leak.

You could perhaps come up with answers for all of these questions (except for the last), but you'd end up with something highly complex and confusing.

Instead, we're going to use the idea of private slots, which are completely different from JS object properties, and we're going to use a new slot lookup syntax. It's all very simple and easy to think about. And it requires no essential extension to the JS object model. Built-in JS objects like Promise are already specified to have just the same sort of private slots that we are proposing here. We're just providing a way for classes written in JS to have the same kinds of slots.

Also, see zenparsing/es-abstract-refs#11 for more information on why we don't want to use a "private" version of Symbols here.

@yanickrochon

This comment has been minimized.

Copy link

yanickrochon commented Nov 20, 2015

Yes, you got what I meant right. And I do understand the extra overhead with adding extra property definition attributes. While I know JavaScript quite well, I have not worked under the hood, on the engine itself, so I am not fully aware on how it is actually implemented in details. I was sure that it was possible to tell if a given property was being accessed from within the same context where it was declared without relying on the property's definition, just like let a; cannot be accessed outside it's parent code block.

How do you prevent leaking the names of private fields to clients that shouldn't know that information? This is probably a fatal information leak.

Well, yes, however pretty much any information can be known through "reflection" anyway, even if not exposed or allowed direct access. Java, for example, makes it possible to modify private fields through reflection... While this is not a good thing, it is nonetheless possible and does allow better DI. I am not a great fan block-boxing everything, since JS is not compiled and anyone have access to source code anyway. Makes the effort pointless for run-time. The idea of "private" properties is not essentially to hide them, because this can already be achieved through enumerable: false, but to prevent modifying them outside of it's declaring context. In essence, allowing getting or setting values to a private property depends if this instanceOf PrototypeOfPrivateAttribute.

Classes are supposed to be a semantic feature and should not de-nature the language itself with OO paradigm.

@zenparsing

This comment has been minimized.

Copy link
Member

zenparsing commented Nov 20, 2015

The idea of "private" properties is not essentially to hide them, because this can already be achieved through enumerable: false, but to prevent modifying them outside of it's declaring context.

Says you! : )

One of the explicit goals of this private state proposal (or any other that has been brought forward) is to allow the creation of "defensive classes", which are indeed secure at runtime. As a subgoal, we want to allow things like DOM classes (and JS built-ins) to be expressible in JS itself.

@melanke

This comment has been minimized.

Copy link

melanke commented Jan 22, 2016

class X {
    private a;
    constructor(a) { this.a = a }
    swap(otherX) {
        let otherA = otherX.a;
        // if otherX.a is private then otherA is undefined
        otherX.a = this.a;
        //if otherX.a is private it will create another variable named a only for this scope
        this.a = otherA;
        //if otherX.a was private then this.a is undefined now
        return otherA;
    }
}

let x1 = new X(1);
let obj = { a: 3 }; //a is public here, isn't possible to define private fields this way
x1.swap(obj); // --> 3
@glen-84

This comment has been minimized.

Copy link

glen-84 commented May 1, 2016

This looks awful. I associate hash symbols with commented-out code. JavaScript has quite a clean syntax – please don't ruin that.

C++, C#, Java, PHP, TypeScript (and probably others) all support private without an ugly character prefix.

I can't comment on the technical details of the implementation, but there must be a way of solving this in an elegant and efficient manner.

@zenparsing

This comment has been minimized.

Copy link
Member

zenparsing commented May 1, 2016

@glen-84 Thanks for commenting. I agree that a leading hash looks terrible. We can't use a private keyword, but maybe there are other leading characters that would look better. What do you think about @?

@glen-84

This comment has been minimized.

Copy link

glen-84 commented May 1, 2016

What do you think about @?

At signs are already "reserved" for decorators, so you'd end up with:

@dec @num = 5;

I want to write:

@dec
private num = 5; // or private static num

Your description in this comment is how I would have imagined it to work.

@zenparsing

This comment has been minimized.

Copy link
Member

zenparsing commented May 1, 2016

I don't want to assume anything about decorators (their syntax or semantics) at this point...

Thanks for linking to my comment : ) I think it's a good documentation of why that solution won't fly.

@glen-84

This comment has been minimized.

Copy link

glen-84 commented May 1, 2016

I don't want to assume anything about decorators (their syntax or semantics) at this point...

Perhaps you're right, but they're already being used via Babel and TypeScript, so I'd be very surprised if the basic syntax changed.

Thanks for linking to my comment : ) I think it's a good documentation of why that solution won't fly.

Like I said, I can't really comment on the implementation, but I still don't believe that this is the only solution.

@zenparsing

This comment has been minimized.

Copy link
Member

zenparsing commented May 1, 2016

For the record, decorators are at stage 1, meaning that proposal is just as likely to change as this proposal.

@erights

This comment has been minimized.

Copy link

erights commented May 2, 2016

Agree with @zenparsing on almost everything. Disagree about # vs @. Despite the fact that both decorators and private fields are technically at stage 1, I really doubt that private fields will get @. Without it, I don't see plausible alternatives to #. Initially it hurt my eyes as well. However, after repeated exposure I rapidly became used to it. Now # simply means private when I see it followed immediately by an identifier.

Although we should strive for syntax which is initially more intuitive when possible, when it is not, don't underestimate how quickly novel syntax becomes natural on repeated exposure.

@glen-84

This comment has been minimized.

Copy link

glen-84 commented May 2, 2016

This is sad. Sure, we can get used to anything (I "got used" to writing PHP for like a decade, because I had to), but that doesn't mean that it's not ugly AF.

I sincerely hope that one or more of the implementers agree, and that a solution can be found that doesn't result in the syntax being butchered.

@sarbbottam

This comment has been minimized.

Copy link

sarbbottam commented May 2, 2016

Please excuse me for being late to the discussion.
I wonder if we are discussing over the prefix, either # or @ at this point of time, whats wrong with anything else, to be precise private as prefix?

@erights

This comment has been minimized.

Copy link

erights commented May 2, 2016

Verbosity. foo.private x vs foo.#x. Also, the space within the first leads to the eye misparsing it. For example, foo.private x.bar does not suggest a parsing equivalent to (foo.private x).bar.

Btw, speaking of verbosity, I prefer foo#x to foo.#x.

@sarbbottam

This comment has been minimized.

Copy link

sarbbottam commented May 2, 2016

Can there be a block level variable withing the class { }, I'm sorry if this already have been discussed earlier.

class Stack {

  const stack = [];
  let top = -1;

  constructor() {}

  push(value ){
    stack.push(value);
    top += 1
  }

  pop() {
    stack.pop(value);
    top += 1
  }

  isEmpty() {
    return top === -1
  }

}
@erights

This comment has been minimized.

Copy link

erights commented May 2, 2016

Funny you should mention that. I like @sebmarkbage 's https://github.com/sebmarkbage/ecmascript-scoped-constructor-arguments proposal. However, since it leverages the intuitions around lexical variable capture, it should only be used to express instance-private instance state, not class-private instance state. Thus it would enhance both private fields and class properties, rather than replacing private fields. For both, it would solve the otherwise unpleasant issues #25 , tc39/proposal-class-public-fields#2 , and wycats/javascript-private-state#11 , by allowing constructor-parameter-dependent initialization expressions.

Starting with https://github.com/sebmarkbage/ecmascript-scoped-constructor-arguments , it would seem to be a natural extension to also allow let and const variable declarations within the class body, as you suggest, to express further lexical-capture-like instance-private instance state. I do not yet have an opinion about whether this additional step would be worth doing.

@yanickrochon

This comment has been minimized.

Copy link

yanickrochon commented May 2, 2016

What about

class Stack {

  static const base = 100;
  static let increment = 0;

  const stack = [];
  let top = -1;

  constructor() {}

  push(value ){
    stack.push((base + value) * increment);
    top += 1
    increment += 1;
  }

  pop() {
    stack.pop(value);
    top -= 1
    increment += 1;
  }

  isEmpty() {
    return top === -1
  }

}
@erights

This comment has been minimized.

Copy link

erights commented May 2, 2016

What would it mean?

@yanickrochon

This comment has been minimized.

Copy link

yanickrochon commented May 2, 2016

Seriously? ...well, here's another example

class Thing {
  // static private field
  static let count = 0;

  static create(name) {
    return new Thing(String(name));
  }

  // private field bound to instance
  let originalName;

  constructor(name) {
    this.name = originalName = name;
    count += 1;
  }

  getName() {
    if (this.name !== originalName) {
     return count + '@' + this.name + ' (' + originalName + ')';
    } else {
     return count + '@' + this.name;
    }
  }
}

new Thing('foo').getName();
// -> "1@foo"
new Thing('bar').getName();
// -> "2@bar"
let t = Thing.create('test');
t.name = 'something';
t.getName();
// -> 3@something (test)"
@sarbbottam

This comment has been minimized.

Copy link

sarbbottam commented May 2, 2016

@yanickrochon two quick questions

  • Why do you need static create(name)? One should be able to new Thing(name)
  • Why String(name) and not new Thing(name)?
@yanickrochon

This comment has been minimized.

Copy link

yanickrochon commented May 2, 2016

@sarbbottam I ran out of ideas 😛 It's only meant to prove a point. The method is useless in itself, but only show usage in syntax. As for String(name)... I don't know!

@erights

This comment has been minimized.

Copy link

erights commented May 2, 2016

This use would be equivalent to just placing the let count = 0 declaration above the class, using real lexical capture rather than trying to emulate it by other means.

Nevertheless, if we allow instance let and const declarations as an extension to https://github.com/sebmarkbage/ecmascript-scoped-constructor-arguments , then perhaps least surprise would lead us to adopt the static forms as well. Interesting.

@yanickrochon

This comment has been minimized.

Copy link

yanickrochon commented May 2, 2016

@erights indeed, the static in context is the same, but is meant for consistency more than usefulness; a syntax sugar to wrap all functionality inside the class and avoid separation of responsibility.

@bakkot

This comment has been minimized.

Copy link
Contributor

bakkot commented Aug 17, 2017

@robbiespeed

Could you explain why?

I don't have a good, clear way of articulating it, I'm afraid. In large part, it just feels like a nontrivial and surprising bit of complexity in how the language works.

As an example, though - you've already noticed there's a confusion about what the receiver of private methods is, which has to be worked around. But if someone has the mental model "private returns an object", then it is surprising that private.x() calls x with the public object as its receiver, rather than the private one. (It's also surprising if f = function(){}, private.x = f, private.x === f is false.) On the other hand, if someone is just thinking "private.x() is a private method invocation", it's surprising if x is not called with the public object as its receiver. If there is a real, reified object which private resolves to, neither is a good option.

I didn't want to make the comment too long but you can imagine how it would look adding many more private fields

I don't think that's a flaw in the current proposal. Indeed, dynamically adding private fields seems likely something we'd actively want to prevent. Private fields are not intended to be used as a map - if you want a private map, put a map on a private field.

@rdking

This comment has been minimized.

Copy link

rdking commented Aug 18, 2017

@ljharb
As promised, I won't beat the dead horse.
However, I would hope that, maybe someday, possibly after writing a language or 2 of your own, you'll come to understand that primitives are in fact instances of intrinsic types. If that were not the case, primitives would be little more than allocated memory with no means of manipulation or display without dropping down to machine language to access the data. The intrinsic type associated with that memory area contains all of the operations that can be performed on that memory. The association between the memory area and its intrinsic type that is a primitive is by its very definition, an instance.

... but you're free to continue seeing it differently if you like. :-)

@rdking

This comment has been minimized.

Copy link

rdking commented Aug 18, 2017

@robbiespeed
I wrote a JS library 2 years ago that implemented encapsulating classes. It worked very similarly to your idea for accessing private data (but not exactly). There's 2 problems with your idea beyond what the others mentioned:

  1. What happens when someone does this:
class Animal {
  constructor (noise) {
    private.noise = noise;
  }
  speakTogether (otherAnimal) {
    return `${private.noise} ${private(otherAnimal).noise}`;
  }
  somethingStupid() {
    return private;
  }
}

That was one of the issues that's got me redesigning my library even now. Your code suggests that private is a Function instance with properties corresponding to the private declarations in the class. At that rate, there's nothing stopping someone from returning it, whether intentionally or otherwise. That breaks encapsulation since the one calling Animal.somethingStupid() could always modify the private function object.

  1. Would the public members of Animal be available as properties on private?
    If so, then you've got another issue, since primitives on private will be separate instances from the primitives on this. The way around that is to have the public properties on this use getters and setters to retrieve the value from private. That costs execution time.

If not, then you've got to contend with your private members only being accessible through private while your public members are only accessible through this. That can get both messy and make for confusing code.

@rdking

This comment has been minimized.

Copy link

rdking commented Aug 18, 2017

Dang.... I guess I can't let this one go after all. I went reading through other articles and ended up remembering something that makes the best possible argument for sealing class instances. Try this:

class SomeClass {
  #x;
  constructor() {
    this.#x = "initialized";
  }
}

According to the FAQ, there should be no means of accessing this.#x from an instance of SomeClass except from within a method of SomeClass called on an instance of SomeClass, right? So what if such a class were in a library, and a developer using the library does this?

var a = new SomeClass();
var b = new SomeClass();
...
//Need to get to the private variable...
SomeClass.prototype.hijack = function() { return this.#x; }
var adotx = a.hijack();

//Even of SomeClass.prototype was sealed...
b.hijack = function() { return this.#x; }
var bdotx = b.hijack();

//Just to be silly, this works too!
function hijack() { return this.#x; }
var silly = hijack.call(b);

Put simply, no matter how you implement private members, as long as the instances are not sealed, the private members are effectively only soft-private. All private members are accessible as long as new methods can be used to probe the scope of a class instance. To that end, I'm amending my previous suggestion:

  1. Freeze all class definitions (Object.freeze(.prototype);). They shouldn't be modifiable anyway. Allowing them to be modified after definition would be akin to adding a spoiler to the factory line for a particular make and model of car to find out later that at that exact moment, all previously built instances of that make and model of car suddenly had that exact spoiler on them as if it had been there since the car was first built.
  2. Seal all class instances;
  3. Alter the semantics of Function.prototype.call and Function.prototype.apply in such a way that their use prevents the [[[Call]]] internal function from exposing the private members of the scope parameter to the function being called in this indirect fashion. Without doing at least this much, the whole encapsulation argument is moot.

Ok... I think I may have turned part of that dead horse into glue.

@bakkot

This comment has been minimized.

Copy link
Contributor

bakkot commented Aug 18, 2017

@rdking, this did occur to us, in fact. In this proposal, referring to a private field is a syntax error outside of the class which it declares it, so that isn't a legal program, and the issue does not arise.

@robbiespeed

This comment has been minimized.

Copy link

robbiespeed commented Aug 18, 2017

I personally haven't been able to grasp the need of rigid encapsulation for private fields. It's my opinion that people should be allowed to break encapsulation if they see fit, because ultimately there will always be a way, whether it's transpiling code or baking in methods that list out and return all private fields.

I see rigidity is a major goal for this proposal, and that's ok. In order to make my proposed syntax rigid in the same way while possible would require adding too much background magic, and would ultimately prove confusing. Like private and private(key) could both be a Proxy that is revoked after method calls (to be clear I don't think this is a good idea).

There is a benefit to having a form of rigid encapsulation which is at the very least providing another option, and if we need more flexibility we can always switch to a WeakMap, or use both.

@glen-84

This comment has been minimized.

Copy link

glen-84 commented Aug 19, 2017

@robbiespeed You should perhaps instead comment in #33.

@rdking

This comment has been minimized.

Copy link

rdking commented Aug 19, 2017

@bakkot
That a good sign. So, does that mean that private fields are only available for classes? Or can private fields be applied to any object? Also, are private fields available in function scopes? That last question is kinda critical since there could potentially be a new disparity between functions and classes. I'm in favor of such disparities, but I can easily understand why others might not be.

@rdking

This comment has been minimized.

Copy link

rdking commented Aug 19, 2017

@robbiespeed
The answer looks like this:
Apple is an example of one of the few software companies that exists that doesn't care if they break their third-party developers by changing OS internal functionality. As such, they are completely free to implement whatever they want without fear of losing major developer support. Microsoft, on the other hand, spends a large portion of their OS development time putting in hacks to support extremely popular programs that made use of hidden internal API's in Windows. This is one of the major reasons Windows seems so unstable and why Microsoft has such difficulties implementing new OS features. Supporting developers who ill-advisedly use unpublished APIs is a problem for the developer and a huge issue for the library maintainer. With JavaScript being such an open language, those of us who create and share libraries would like to be able to do so without the fear of breaking users and risking losing their support.

@littledan

This comment has been minimized.

Copy link
Member

littledan commented Aug 23, 2017

@rdking In this proposal, private fields are only available on classes. Thanks for explaining the need for encapsulation.

@rdking

This comment has been minimized.

Copy link

rdking commented Aug 23, 2017

@littledan
So then, by this proposal, ```class`` will no longer simply be syntactic sugar over prototypes and constructor functions? If this is true, then that raises more questions for me.

@bakkot

@rdking, this did occur to us, in fact. In this proposal, referring to a private field is a syntax error outside of the class which it declares it, so that isn't a legal program, and the issue does not arise.

The only reason I can think of that someone would want to attach a function or member to an instance or class prototype is to add extra functionality to the instance or type. Right now, that's all fine and dandy since everything in a class is public. Once private members are possible, and given that nothing outside the class's declaration will be able to see the private members, what sense does it make to continue to allow things to be attached to the type or instance after the fact if the additions won't have access to the private state of the type or instance?

If the syntactic sugar parity is being broken (i.e. no private fields on non-class instance objects), then does it even make sense for the .prototype on a class to be writable or even exposed? I raise the same question about the .__proto__ on an instance object, I assume that __proto__ is necessary to expose on instances due to the definition of an object in JavaScript.

What I would like, if anyone can do it, is a good, logical example of why it's a good idea to allow class to be extensible without using extends, and a class instance to be modified by means other than the public interface provided by the instances type. Note that "because arbitrary object allow you to do so" does not count as a valid argument since class instances will have capabilities not found in arbitrary objects, and are thus, not arbitrary objects. I really would like to be convinced that this is a good course of action if this is the intent. As I see it, all of the issues surrounding encapsulation would be solved if only class produces frozen definitions and class instances are sealed.

@ljharb

This comment has been minimized.

Copy link
Member

ljharb commented Aug 23, 2017

class is already more than simple sugar; class Foo extends Array {} ; const i = new Foo(); Array.isArray(i) && i instanceof Foo && i instanceof Array and a few other things can't be done with prototypes.

@rdking

This comment has been minimized.

Copy link

rdking commented Aug 24, 2017

@ljharb
I'm afraid you're mistaken there. While you can't do this with just prototypes, you can indeed do it with just plain old JavaScript. It looks something like this:

function Foo() {
   //We are inheriting from  a native object class, so that has to be the foremost object.
   var newThis = [];
   Object.setPrototypeOf(newThis, Foo.prototype);
  //Do your other constructor work here
   return newThis;
};

//Standard inheritance
Foo.prototype = [];
Foo.prototype.constructor = Foo;
//Add your Foo members here!

The problem is that the Array object you get back when creating a new Array() is exotic. The solution is to just make sure that there's an Array instance as the top level object. The stuff in the constructor is the sugar that makes it work. You still get back an instanceof Foo, but now all new Foo() instances can properly use their inherited Array functions. So, barring the syntax, there's nothing class currently gives you that can't be done in ES5.

@doodadjs

This comment has been minimized.

Copy link

doodadjs commented Aug 24, 2017

@rdking Your solution just returns a new array from the constructor and changes its proto. So the constructor is useless. @ljharb is right, the only way to extend a native type is by using an ES6 class.

EDIT: I have two underscores before and after "proto", but GitHub doesn't show them and make my text in bold. I'm sorry.

@rdking

This comment has been minimized.

Copy link

rdking commented Aug 24, 2017

@doodadjs
What you don't seem to get is that this is precisely what's going on under the hood! But you don't have to believe me. Just try this:

class Foo extends Array {};
var a = new Foo();
Object.setPrototypeOf(a, null);
Array.isArray(a); //returns true

Array.isArray() checks to see if the object you passed is an Array native object instance defined by the currently running instance of the JavaScript runtime (i.e. if a is of the same internal type as []). So, no matter how it looks to you, the code I gave is accurate and precisely describes how native types are being extended using class ... extends ....

The reason it has to be done this way is because all the functions on native types that are native functions expect to work with native instance objects. What we get in JavaScript when using one of these types is a mapping in JS to the actual native object that was created. Since the native functions expect to be able to use native objects that match the parameter requirements, the only thing that can be done is to ensure that the this object when used with a native function is always of the correct native type (i.e. make the Array instance be the actual instance object).

@rdking

This comment has been minimized.

Copy link

rdking commented Aug 24, 2017

Getting back to the original topic:
It looks like many thoughts have been tried and failed. Here's another twist on it:

  • Given that the notion of [[Private Slots]] is being used, how about letting each class have 2 such slots, one for private members, and one for protected members.
  • Each such slot should hold a single regular object with initially null prototype whose members are those declared using the private and protected keywords, respectively.
  • The [[Property Descriptor]] type should include both an "isStatic" boolean and a "privilege" string accepting one of ("undefined", "private", "protected", "public"), where undefined is treated as "public". (Not strictly necessary, but allows for dynamic creation of private members.)
  • Creation of an instance would entail creating new objects in the [[Private Slots]] of the instance, each with a __proto__ referencing the object in the corresponding slot on the class.
  • Extending a class would include setting the prototype of the object in the protected [[Private Slot]] of the class to be the object in the same slot of the super class.
  • Calling a method declared in the class with an instance of the class would entail adding both objects in the [[Private Slots]] of the instance to the lexical scope of the method before adding the methods own lexical scope, and also temporarily adding both private slot objects to the prototype chain of the instance object as the 1st and 2nd prototypes. (This would enable both foo = 1; and this.foo = 1; for a private foo; without complication.)
  • Any call to a non-member or a return from the called method would restore the original prototype chain, ensuring private & protected members are not accessible from outside the class.
  • Returns from external methods back into internal methods would restore the modified prototype chain, assuring no interruption of functionality.

In theory, following the above description, in combination with freezing classes and sealing instances, would address every issue that I've seen in just about every topic surrounding this proposal, save for 1. There would still be no way to dynamically add new methods or properties to the public API of a class. However, given that this proposal doesn't allow for newly added methods to access private data, I don't see the need for such a capability. Everything else that's needed can be handled via extension and the decorator pattern(for mixins).

@NE-SmallTown

This comment has been minimized.

Copy link

NE-SmallTown commented Sep 29, 2017

Is there any summary on this? Sorry for that I don't view all comments because there are too many of them.

@bakkot

This comment has been minimized.

Copy link
Contributor

bakkot commented Sep 29, 2017

@NE-SmallTown See the FAQ.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment