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

What about protected methods, better declaration of private fields, private async functions, and destructuring? #21

Closed eddyw closed 6 years ago

eddyw commented 6 years ago

How could we allow a private field to be shared only when subclassing but not when it's an instance? Why not protected fields were not proposed? Have you thought if in the future somebody does propose them? (we are running out of characters)

I know it was discussed many times why $ was chosen (I read the FAQs). It's ugly but I get it. However, that doesn't mean there cannot be another way of declaring them. What about this?:

class A {
  static prop0 = 123
  private prop1 = 123
  prop2 = 123 // public property
  method() { // public method
    console.assert(new.target.prop0 === 123) // static
    console.assert(this.#prop1 === 123) // private <- accessing changes
    console.assert(this.prop2 === 123) // public
  }
}
console.assert(A.prop0 === 123) // static (devs know it's not A.static.prop0)

Declaring them that way makes more sense besides it doesn't break the current proposal's idea. However, if somebody comes up with "this may be confusing..." or similar, devs do not seem confused using static [prop], they know how to access a static var (new.target[propName]). In a similar way, private [prop] won't make it more confusing, devs will know to access with this.#[prop].

An example where it will look better:

class A {
  #$ = jQuery
  #_ = lodash
  method() { this.#$(...) }
}
class A {
  private $ = jQuery // You still have to access with this.#$
  private _ = lodash // You still have to access with this.#_
  method() { this.#$(...) }
}

What about async functions?

class A {
  #async method() {}
  // or
  async #method() {}
}

How will it behave with destructuring. For instance (with no Private fields):

class A {
  prop = 10
  constructor() {
    const { method } = this
    method()
  }
  method() { console.log('Not bound to "this" but works') }
}

With private fields:

class A {
  prop = 10
  constructor() {
    const { #method } = this // or { method } = this# ???
    #method() // ??? or not allowed, throws? (but what)
  }
  #method() { console.log('Not bound to "this" but works') }
}

~Are private fields not enumerable?, I mean, is it possible to get all the names of private fields from inside a class? If they are not enumerable, how does it affect applying a function decorator and changing the descriptor to enumerable: true?~ got it :P

How does it act with Proxy?

class MyProxy extends Proxy {
  constructor() {
    return new Proxy(this, {
      get(target, propName) {
        if (typeof target[propName] === 'function')
          return target[propName]
        console.log(`Accessing [[${propName}]]`)
      }
    })
  }
}
class A extends MyProxy {
  one = 5
  prop = 10
  #prop = 20
  method() {
    this.prop
    this.#prop
    this.#method()
  }
  #method() {
    this.one
  }
}
new A().method()
// outputs: Accessing [[prop]]
// outputs: ??? Accessing [[#prop]]
// outputs: ??? Accessing [[one]] <- as a result of calling this.#method, does the trap still work?

So far I have more questions that actually use cases since I'm not pretty sure how much does it affect everything we know about JS. Now we have block scope vars and function scope vars. Maybe explaining how to categorize private block/function scope vars will clarify better all questions.

littledan commented 6 years ago

Async private methods are supported in this proposal, as async #method() { }. Private fields and methods are not forwarded through Proxies. Protected is proposed to be handled in a follow-on decorators proposal. This seems to be mostly a dup of #59, so closing this issue.