tc39 / proposal-class-fields

Orthogonally-informed combination of public and private fields proposals
https://arai-a.github.io/ecma262-compare/?pr=1668
1.72k stars 113 forks source link

Can private fields be functions and how are they bound? #141

Closed seansd-zz closed 6 years ago

seansd-zz commented 6 years ago

I have 2 examples below that call out the scenario, and there are 3 questions that need to be answered, all of which I think need clarity in the spec.

  1. Can private fields (well really public or private) be a function?
  2. If private fields can be defined as function can they access super?
  3. Does it matter how a private field how said field is defined as a function? You can see via the examples and my comments below this could be very confusing / unclear. I'm pretty sure the 2nd example would work with the caveat that you could not access super in that case (though i think that's true in both examples but we'll see).
class MyClass {

    #toString  = () => {
        /*
         * b/c of the arrow function in theory
         * being lexically scoped, what is 'this'
         * here in this case, i assume it's whatever
         * 'this' was at time of creating the function
         * and therefore is undefined
        */

        return this.#message;
    }

    #message = '';

    constructor(msg) {
        this.#message = String(msg);
    }

    toString() {
        return this.#toString();
    }
}
class MyClass {

    #toString = function() {
        /*
         * Here we use a normal function, so the this is implicit
         * from the caller, so this should work I would think,
         *
        */

        return this.#message;
    }

    #message = '';

    constructor(msg) {
        this.#message = String(msg);
    }

    toString() {
        return this.#toString();
    }
}
ljharb commented 6 years ago

With an arrow function, I believe this would be the instance, just like in public fields.

With a normal function, it'd use normal javascript rules for determining the receiver.

littledan commented 6 years ago
  1. Yes
  2. Yes, in particular, super property access (i.e. super method calls) is defined "lexically", so you can do it within an arrow function that's part of a field initializer, but it won't work in a function literal.
  3. Both of your examples would work, as far as I can tell. Yes, it's possible to write confusing code in JavaScript, but do you think this feature makes things more confusing? Maybe I've been staring at the spec too long, but I like to think that this is just following the existing logic.
seansd-zz commented 6 years ago

Well the arrow function actually having access to super, and this being bound to the actual object is not how arrow functions work traditionally. That's not truly lexically bound since there is not a this pointer at the point of the function being defined, so yeah I'd say that's something that should be cleared up.

seansd-zz commented 6 years ago

I mean I guess we're kinda saying it's bound the the "internal slot that you can only access through 'this' or 'super' but that's pretty odd.

littledan commented 6 years ago

Well the arrow function actually having access to super, and this being bound to the actual object is not how arrow functions work traditionally

I'm not sure what you mean. Arrow functions work in field initializers just like they work in methods.

loganfsmyth commented 6 years ago

@seansd The way to think about it is, the body of a class field initializer automatically introduces a scope with a this binding. It isn't that arrow functions are special-cased, it is just how field initializers are specified. For example, even without arrows, if you have something like

class Foo {
  one = 1;
  two = this.one + 1;
}

the this in that class property is the instance, so new Foo().two === 2.

So if you use an arrow function, since they access the lexical this, they pick up the same instance-level reference.

seansd-zz commented 6 years ago

Yeah I'm getting that Normally I think of it more in terms of scope you can see I guess, for example:

(function() { 
     function memoize(propName) {

        let a = () => {
            return this[propName];
        }
        return a;
     }

    let o = {x:100,y:100};
    let getX = memoize.call(o, 'x');

    return getX();
})();

But I understand / get it. . .

Curious can you define a named regular function inside a class and does that become a field?

   class Foo {
         function doSomething() { }
         function #doSomething() { } 
   }

Or does there have to be an '=' if you want to set the field value?

ljharb commented 6 years ago

You can’t do that at the moment, and yes you’d need an = for it to be a field.

ljharb commented 6 years ago

You can, though, define public or private methods:

foo() {}
#bar() {}
seansd-zz commented 6 years ago

ah, ok that works almost as well. . . the only thing that still concerns me then is the fact that any public method still 'needs' to be guarded against the fact that you may not have a stable 'this' pointer, which is discussed in the closed thread here: https://github.com/tc39/proposal-class-fields/issues/119

The reason i bring it up again is b/c you need a 'this' and without some syntax / allowance for guarding , developers are forced to into something like if (this && this instanceof MyClass) in EVERY public method if they are trying to be safe. I realize that can be considered an edge case, and I could maybe live with it being an addition to the spec, but that's been a problem for a long time even before class syntax. Either way we can probably close this particular issue, but I still think it's worth visiting the fact that 'this' is not stable especially if we are going to have public and private fields.

littledan commented 6 years ago

Yes, for better or worse, JS doesn't give you many guarantees from the receiver of a method, and this proposal gives programmers more ways to observe that. Closing per https://github.com/tc39/proposal-class-fields/issues/141#issuecomment-429498359

rezemble commented 5 years ago

You can, though, define public or private methods:

foo() {}
#bar() {}

at the current stage, you cannot.

#bar = function() {}

works fine, however

#bar() {}

does not.

nicolo-ribaudo commented 5 years ago

That is covered by another stage 3 proposal: https://github.com/tc39/proposal-private-methods