Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Function as a Class Property. Lack of description? #293

Closed
dhilt opened this issue Feb 1, 2020 · 11 comments
Closed

Function as a Class Property. Lack of description? #293

dhilt opened this issue Feb 1, 2020 · 11 comments

Comments

@dhilt
Copy link

dhilt commented Feb 1, 2020

There are tons of articles/docs mentioning Stage 3 Proposal in context of using Arrow functions as Class properties. Like, now we can use

class Foo {
  bar = () => console.log(this.baz);
  constructor(baz) { this.baz = baz; }
}

const foo = new Foo(1);
myElement.addEventListener('click', foo.bar);

instead of

class Foo {
  constructor(baz) { this.baz = baz; }
  bar() { console.log(this.baz); }
}

const foo = new Foo(1);
myElement.addEventListener('click', foo.bar.bind(foo));

I'm just trying to get a confirmation if it's something legal from the Stage 3 Proposal perspective. I've walked through all Stage 3 related repos and seems this one is the most appropriate. And here we see only Primitive as a Class property example in the Readme:

class Counter extends HTMLElement {
x = 0;
...
}
In the above example, you can see a field declared with the syntax x = 0

Does the Stage 3 Proposal really allow to use functions in that capacity or it's a kind of React/Babel/etc fantasy? If yes, it does, then maybe it would be a good idea to add a few words or even an example in the description?

@ljharb
Copy link
Member

ljharb commented Feb 1, 2020

Yes, it’s allowed; any value is allowed in a class property.

@a-ejs
Copy link

a-ejs commented Apr 20, 2020

why would you want that though? wouldn't that create a new function for every instance of your class, as well as break prototype inheritance?

(also weird how arrow functions that normally don't have their own this deviate from that behavior in this case, even though that's like almost their entire point)

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Apr 20, 2020

wouldn't that create a new function for every instance of your class

Not always:

function fn() {}

class A { x = fn };

Also, it's consistent with how this code creates a new object for every instance of the class:

class A { x = {} }

as well as break prototype inheritance?

Yes, it would be similar (modulo the defineProperty thing) to

class A {
  constructor() {
    this.fn = function () {};
  }
}

If you want it to respect the normal prototype chain you can use an "old-style" method. Also, having a runtime error when a value is a function is super inconsistent with the rest of the language.

also weird how arrow functions that normally don't have their own this deviate from that behavior in this case, even though that's like almost their entire point

Arrow functions in class fields behave like normal arrow functions: they get this from the outer scope.

class A {
  x = this;
  y = () => this;
}

let { x, y } = new A;
x === y();

@a-ejs
Copy link

a-ejs commented Apr 21, 2020

guess i shouldn't have phrased that as a question.

@littledan
Copy link
Member

Thanks for answering these questions, @ljharb and @nicolo-ribaudo .

@a-ejs
Copy link

a-ejs commented Apr 25, 2020

my question wasn't answered, actually:

why would you want that though?

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Apr 25, 2020

The biggest usecase is to use arrow functions. Normal methods are like normal functions, thus don't capture this.

If you are looking for an example without arrow functions, here it is:

class ClickLogger {
  el;
  listener = function () { console.log("click!"); }

  constructor(el) {
    this.el = el;
  }

  attach() { this.el.addEventListener("click", this.listener) }

  detach() { this.el.removeEventListener("click", this.listener) }
}
const logger1 = new ClickLogger(document.body);
const logger2 = new ClickLogger(document.body);

logger1.attach();
logger2.attach();

// Click on the document. It will log "click!" twice, because you have two loggers

logger2.detach();


// Click on the document. It will log "click!" once, because you still have one logger

If you use a normal class method, it won't attach the second logger and after you detach the first one it won't log anything, even if there is still the first logger.

That said, functions are normal value in JavaScript, and disallowing some specific values would be inconsistent with the rest of the language. I cannot think of a valid use case to allow passing the exact "JavaScript is a horrible language!!! 2732" string as a parameter to an arrow function, but it doesn't mean that we should forbid it because, exactly like functions, it's a normal JavaScript value 🤷.

Also, note that this proposal doesn't force you to use functions in class fields. If for your use case it's better to use a normal class method, you should still use it.

@a-ejs
Copy link

a-ejs commented Apr 25, 2020

i never said anything about disallowing functions in 'fields'.

my point is any situation where a function 'field' can replace a prototype method is either an improper use of fields (after) or a weird use of methods (before).

you also mentioned inconsistency with the rest of the language multiple times, which is funny because it perfectly describes the entire idea of statements with delayed execution outside of functions (ie. this proposal).

@rdking
Copy link

rdking commented Apr 27, 2020

my point is any situation where a function 'field' can replace a prototype method is either an improper use of fields (after) or a weird use of methods (before).

IMO, the opinion of @a-ejs is true regardless of whether the field is public or private. No doubt, this is an unpopular position.

@ljharb
Copy link
Member

ljharb commented Apr 27, 2020

I share that opinion - functions shouldn’t go in class fields/own data properties, except for bound prototype methods.

However, it wouldn’t be consistent or appropriate for the language to enforce an arbitrary distinction between values, especially when it wouldn’t be enforceable in non-field data properties.

@rdking
Copy link

rdking commented Apr 28, 2020

While I'm no longer arguing for a change (since I recognize it to be futile), I must note that it is the design of this proposal that precludes such a reasonable restriction, not the language itself. However, I also understand that this is itself based on a fundamental difference between our (mine vs the TC39 consensus) understandings of the nature of a class.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants