Classes & Constructors

  • 9.1 Always use class. Avoid manipulating prototype directly.

    Why? class syntax is more concise and easier to reason about.

    1. // bad
    2. function Queue(contents = []) {
    3. this.queue = [...contents];
    4. }
    5. Queue.prototype.pop = function () {
    6. const value = this.queue[0];
    7. this.queue.splice(0, 1);
    8. return value;
    9. };
    10. // good
    11. class Queue {
    12. constructor(contents = []) {
    13. this.queue = [...contents];
    14. }
    15. pop() {
    16. const value = this.queue[0];
    17. this.queue.splice(0, 1);
    18. return value;
    19. }
    20. }

  • 9.2 Use extends for inheritance.

    Why? It is a built-in way to inherit prototype functionality without breaking instanceof.

    1. // bad
    2. const inherits = require('inherits');
    3. function PeekableQueue(contents) {
    4. Queue.apply(this, contents);
    5. }
    6. inherits(PeekableQueue, Queue);
    7. PeekableQueue.prototype.peek = function () {
    8. return this.queue[0];
    9. };
    10. // good
    11. class PeekableQueue extends Queue {
    12. peek() {
    13. return this.queue[0];
    14. }
    15. }

  • 9.3 Methods can return this to help with method chaining.

    1. // bad
    2. Jedi.prototype.jump = function () {
    3. this.jumping = true;
    4. return true;
    5. };
    6. Jedi.prototype.setHeight = function (height) {
    7. this.height = height;
    8. };
    9. const luke = new Jedi();
    10. luke.jump(); // => true
    11. luke.setHeight(20); // => undefined
    12. // good
    13. class Jedi {
    14. jump() {
    15. this.jumping = true;
    16. return this;
    17. }
    18. setHeight(height) {
    19. this.height = height;
    20. return this;
    21. }
    22. }
    23. const luke = new Jedi();
    24. luke.jump()
    25. .setHeight(20);

  • 9.4 It’s okay to write a custom toString() method, just make sure it works successfully and causes no side effects.

    1. class Jedi {
    2. constructor(options = {}) {
    3. this.name = options.name || 'no name';
    4. }
    5. getName() {
    6. return this.name;
    7. }
    8. toString() {
    9. return `Jedi - ${this.getName()}`;
    10. }
    11. }

  • 9.5 Classes have a default constructor if one is not specified. An empty constructor function or one that just delegates to a parent class is unnecessary. eslint: no-useless-constructor

    1. // bad
    2. class Jedi {
    3. constructor() {}
    4. getName() {
    5. return this.name;
    6. }
    7. }
    8. // bad
    9. class Rey extends Jedi {
    10. constructor(...args) {
    11. super(...args);
    12. }
    13. }
    14. // good
    15. class Rey extends Jedi {
    16. constructor(...args) {
    17. super(...args);
    18. this.name = 'Rey';
    19. }
    20. }

  • 9.6 Avoid duplicate class members. eslint: no-dupe-class-members

    Why? Duplicate class member declarations will silently prefer the last one - having duplicates is almost certainly a bug.

    1. // bad
    2. class Foo {
    3. bar() { return 1; }
    4. bar() { return 2; }
    5. }
    6. // good
    7. class Foo {
    8. bar() { return 1; }
    9. }
    10. // good
    11. class Foo {
    12. bar() { return 2; }
    13. }