18

I'm struggling to understand the difference between defining a property inside vs outside the constructor. In the below example both properties are accessible on the instance in the same way, what is the difference?

class Foo {
  constructor() {
    this.bar = 1;
  }
  baz = 1;
}
const foo = new Foo();
console.log(foo.bar, foo.baz); // 1 1
Max888
  • 3,089
  • 24
  • 55

3 Answers3

16

They do the same thing.

Defining the property outside the constructor is new class field syntax. If you need to support older browsers, either use the constructor method, or use Babel to transpile the code first down to older syntax.

Note that class fields run before the constructor finishes - either right after the super call, if there is one, or at the very beginning of the constructor.

class Foo {
  prop = console.log('Class field running');
  constructor() {
    this.prop2 = console.log('Constructor running');
  }
}
const foo = new Foo();

Related: Private fields (also very new syntax) must be initialized outside of the constructor, so that the class can understand which fields are private at compile time:

class Foo {
  constructor() {
    this.bar = 1;
    // Not OK:
    // this.#foo = 1;
  }
  // OK:
  #baz = 1;
}
const foo = new Foo();
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
4

Constructor initialisation allows for using values from the class instantiation, so they may be different for each instance, while properties defined in the class scope are global to all instances.

If you want to initialise a property with values from the instance, you need to do it in the constructor. Normally, if you want an attribute to be general to all instances, you would define it outside of the constructor, but you can also do it inside if you want.

  • 1
    "...while properties defined in the class scope are global to all instances." This is either very misleading or just wrong. See "Public field declarations" at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes. Fields defined at class scope are not "global" (maybe you mean "static"?). They are per-instance. See also [this answer](https://stackoverflow.com/a/64436568/99717). – Hawkeye Parker May 06 '22 at 21:37
2

Actually, defining this.bar within the constructor is considered dirty code in ES6. You can expand any object by adding a property just the way you did. This is the way how it is done in ES5 and lower.

ES6 is mainly just syntactic sugar, meaning all you class definitions get boiled down to "native" JavaScript code. Behind the scenes, your definition of baz will end up in the transcript exactly the way you defined bar. However, for readability, the syntactic sugar was added in ES6 to define a property in a more class-like fashion.

Psi
  • 6,387
  • 3
  • 16
  • 26
  • 3
    Dirty or not, when `bar` depends on e.g. a constructor argument, you have to initialize it in the constructor anyway. – kungfooman Jun 16 '22 at 10:14
  • 1
    Initialize, yes, but not defining by just assigning. In ES5, this is totally fine, but considered dirty in ES6. You are expected to declare at least every property you are going to use for a particular class. – Psi Jun 16 '22 at 10:37
  • 2
    I don't know who expects that, not even TypeScript, they see the constructor assignments as property declarations. Which imo also reduces amount of useless/fluff/sugar code. – kungfooman Jun 16 '22 at 10:40
  • Just because it's not _required_ doesn't mean it's not still good practice. It improves readability and maintainability of your code. And that's why I didn't say "it's invalid code" but instead said "it's considered dirty code" – Psi Jun 16 '22 at 10:47