What are "class fields" in JavaScript?
Asked Answered
O

3

15

I was reading about JavaScript classes, and came across this term "public class fields syntax". On digging a bit deeper into it I came across this Babel's documentation on class properties.

Can someone please explain - implementation-wise what are the use-cases for this new syntax? (What solutions/benefits does it offer to JavaScript, which were missing so far?)

Here's an example below (ran without errors in Google Chrome):

class Person {
    firstName = "Mike";
    lastName = "Patel";
    
    // this is a public class field syntax
    getName = () => {
      return this.firstName + " " + this.lastName;
    };
}

var p = new Person();

console.log(p.firstName); // Mike
console.log(p.lastName); // Patel
console.log(p.getName); // () => { return this.firstName + " " + this.lastName; }
console.log(typeof p.getName); // function
console.log(p.getName()); // Mike Patel
Outdare answered 22/8, 2019 at 11:40 Comment(7)
What do you mean "special"? If you expect this to do some strange and unintuitive thing - then no, it does exactly what you probably think - it creates two properties. It's simply new syntax that wasn't available at first. In Firefox 68 this throws SyntaxError: fields are not currently supportedSaskatchewan
@Saskatchewan I checked in Google Chrome, and it executed without any errors.Outdare
This might be helpful: medium.com/@jacobworrel/…Unroof
@AadityaSharma again, that's in Firefox. It's newer syntax, so it's not completely implemented everywhere. That was the point. That's the only thing "special" about it but I don't know if that's what you consider "special" or not.Saskatchewan
@Saskatchewan I meant, how is it different than regular syntax to define a function? Every syntax has it's own need and benefits, that's what I meant to ask -- that what solutions/benefits does this "class fields syntax" offer to JavaScript which weren't already there?.Outdare
@AadityaSharma so, rather than "what's special", you are after "what is the use-case". Because the syntax itself isn't special. The use case is why you'd go for this rather than different code.Saskatchewan
@Saskatchewan I've edited the question as per your suggestions. Please have a look.Outdare
S
29

Simply put, the reason to use this is ease of understanding the code. Without class field declarations, you would do something like:

class Person {
  constructor() {
    this.firstName = "Mike";
    this.lastName = "Patel";

    this.getName = () => {
      return this.firstName + " " + this.lastName;
    };
  }
}

var p = new Person();

console.log(p.firstName); // Mike
console.log(p.lastName); // Patel
console.log(p.getName); // () => { return this.firstName + " " + this.lastName; }
console.log(typeof p.getName); // function
console.log(p.getName()); // Mike Patel

This works but now you have both the callable getName() and the rest of the plain instance properties all collected in the constructor. You could have even more which means that your class definition would look rather meaningless overall:

class MyClass() {
  constructor(someArg) {
    this.foo1 = 1;
    this.foo2 = 2;
    this.foo3 = 3;
    this.foo4 = someArg;

    this.bar1 = () => {}
    this.bar2 = () => {}
    this.bar3 = () => {}
    this.bar4 = () => {}
  }
}

and so on. Again, everything is in the constructor. If you have a lot of code, it becomes harder to read what is what. And if the constructor takes any arguments, then you have the extra overhead of keeping track of those. Therefore, it is hard to read, hard to maintain, all for no real benefit. You are stuffing everything in the same place.

With class field declarations, you separate them and you get

class MyClass() {
  /* properties - do not depend on the constructor*/
  foo1 = 1;
  foo2 = 2;
  foo3 = 3;
  foo4; /* this is a property that this class will have - 
          I do not need to look at the constructor to know about it */

  /* easy to see what the constructor does that is only about *constructing* the object */
  constructor(someArg) {
    this.foo4= someArg;
  }

  /* callable field are separated from the rest of the simple properties and construction logic */
  bar1 = () => {}
  bar2 = () => {}
  bar3 = () => {}
  bar4 = () => {}
}

So, all in all, it is not revolutionary but it is slightly nicer syntax that makes it easier to express what a class has.

Saskatchewan answered 22/8, 2019 at 12:11 Comment(9)
Late to JS game with one ques on this topic, understand class field proposal aims to simplify the constructor method. Is this something we're recommended to use?Reredos
@TommyLeong I'd say yes. I see no real benefit of declaring everything in the constructor. The two methods are either equal or class fields are superior.Saskatchewan
So it doesn't matter whether if this/(other) proposal go through stage 4, we can use it? There's no issue in using them? New to TC39 too.Reredos
@TommyLeong if you're confident the environment you're running your code supports class fields or at least you use Babel, then there shouldn't be a problem.Saskatchewan
I wouldn't call method1 = () => {} a "method", and certainly not a "method declaration". It's an assignment (or initialisation) of an own property with an arrow function expression. Method definitions use method1() {…} syntax.Gessner
@Gessner much appreciated. I was indeed sloppy with the terminology I used. I re-worded to avoid confusion.Saskatchewan
But aren't class methods saved into the prototype? In your first example above, since getName method is in the constructor method, it would be created in every object. If it was just a usual class method, then it would've been a part of Person.prototype object, and thus wouldn't be reproduced in every object that the class creates.Octroi
@ChinmayGhule it was an illustrative example to convey the idea of class fields. Not a guide for how to write classes. Yes, a regular method is probably better in most cases. Some times you might want to assign a function explicitly for some reason or another. Both are normal usages. Depends on the context which one would be more appropriate. I'd say in most cases, regular methods are preferable.Saskatchewan
Note that different from python, class C {a = 1;} does not define a static variable, i.e. C.a === undefined.Halfcaste
P
7

To quote from the class fields proposal

By declaring fields up-front, class definitions become more self-documenting; instances go through fewer state transitions, as declared fields are always present.

The introduction of class fields also allows for private class fields, which also come with a few benefits:

By defining things which are not visible outside of the class, ESnext provides stronger encapsulation, ensuring that your classes' users don't accidentally trip themselves up by depending on internals, which may change version to version.

Porty answered 22/8, 2019 at 12:6 Comment(0)
S
-1

It originates from this proposal where an "issue" is being tackled down.

PRE-PROPOSAL

Assume that you want to have a class Foo that holds a default attribute, then you can write inituitvely the following

class Foo {
  defaultAttribute = 'default';
  getDefault() { return this.defaultAttribute; }
}

Remember that the proposal here above did not got implemented atm. The defaultAttribute = '...' is being ignored when setting up the class object (when compiling). It is even not a part of the prototypes or field members (of the function object). That is because the defaultAttribute is not picked up by the compiler. Therefore you cannot do foo.defaultAttribute.

Calling getDefault() will throw an error here because it is undefined at that moment. That function does work if you provide a value to defaultAttribute in the constructor;

class Foo {
  defaultAttribute = 'default';
  constructor() {
    this.defaultAttribute = 'hello';
  }
  getDefault() { return this.defaultAttribute; }
}

In this situation, the defaultAttribute is set to 'Hello' but it did not override the original variable with 'default'.

POST-PROPOSAL

With that proposal, the "ignoring" problem is tackled down that you can do that what you just described:

class Foo {
  defaultAttribute = 'default';
  getDefault() { return this.defaultAttribute; }
}

With this, you can skip the use of constructor()

class Foo1 {
  defaultAttribute = 'default';
  getDefault() { return this.defaultAttribute; }
}

class Foo2 {
  defaultAttribute = 'default';
  constructor() {this.defaultAttribute = 'hello';}
  getDefault() { return this.defaultAttribute; }
}

const foo1 = new Foo1();
const foo2 = new Foo2();

console.log(foo1.getDefault());
console.log(foo2.getDefault());
Splint answered 22/8, 2019 at 12:17 Comment(1)
"The defaultAttribute = '...' is being ignored when setting up the class object (when compiling)" - no, it is not. Before the implementation of the proposal, intuitively writing that will simply cause a syntax error. "Calling getDefault() will throw an error here because it is undefined at that moment." - nope. Accessing non-existing properties in JS just returns undefined, no error is thrown.Gessner

© 2022 - 2024 — McMap. All rights reserved.