Skip to main content

Classes & constructors

Prefer the class syntax and ES modules over prototype manipulation and CommonJS.

9.1 Always use class

Avoid manipulating prototype directly. class syntax is more concise and easier to reason about.
class Queue {
  constructor(contents = []) {
    this.queue = [...contents];
  }
  pop() {
    const value = this.queue[0];
    this.queue.splice(0, 1);
    return value;
  }
}

9.2 Use extends for inheritance

extends is the built-in way to inherit prototype functionality without breaking instanceof.
class PeekableQueue extends Queue {
  peek() {
    return this.queue[0];
  }
}

9.3 Methods can return this for chaining

Returning this from methods enables fluent method chaining.
class Jedi {
  jump() {
    this.jumping = true;
    return this;
  }

  setHeight(height) {
    this.height = height;
    return this;
  }
}

const luke = new Jedi();

luke.jump()
  .setHeight(20);

9.4 Custom toString() methods

It’s okay to write a custom toString() method. Make sure it works successfully and causes no side effects.
class Jedi {
  constructor(options = {}) {
    this.name = options.name || 'no name';
  }

  getName() {
    return this.name;
  }

  toString() {
    return `Jedi - ${this.getName()}`;
  }
}

9.5 Avoid useless constructors

Classes have a default constructor if one is not specified. An empty constructor or one that only delegates to a parent class is unnecessary. eslint: no-useless-constructor
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
    this.name = 'Rey';
  }
}

9.6 Avoid duplicate class members

eslint: no-dupe-class-members Duplicate class member declarations silently prefer the last one. Having duplicates is almost certainly a bug.
class Foo {
  bar() { return 1; }
}

// or
class Foo {
  bar() { return 2; }
}
Duplicate class members compile without error. The last definition silently wins, making this a common source of hard-to-spot bugs.

9.7 Class methods should use this or be static

eslint: class-methods-use-this Class methods should use this or be made into a static method unless an external library or framework requires using specific non-static methods. Being an instance method should indicate that it behaves differently based on properties of the receiver.
// good - this is used
class Foo {
  bar() {
    console.log(this.bar);
  }
}

// good - constructor is exempt
class Foo {
  constructor() {
    // ...
  }
}

// good - static methods aren't expected to use this
class Foo {
  static bar() {
    console.log('bar');
  }
}

Modules

Use ES module syntax (import/export) consistently.

10.1 Always use modules

Always use modules (import/export) over a non-standard module system. You can always transpile to your preferred module system.
// best
import { es6 } from './AirbnbStyleGuide';
export default es6;

// ok
import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;

10.2 Do not use wildcard imports

This ensures you have a single default export.
import AirbnbStyleGuide from './AirbnbStyleGuide';

10.3 Do not export directly from an import

Having one clear way to import and one clear way to export makes things consistent.
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;

10.4 Only import from a path in one place

eslint: no-duplicate-imports Having multiple lines that import from the same path can make code harder to maintain.
import foo, { named1, named2 } from 'foo';

// or
import foo, {
  named1,
  named2,
} from 'foo';

10.5 Do not export mutable bindings

eslint: import/no-mutable-exports Mutation should be avoided in general, but in particular when exporting mutable bindings. Only constant references should be exported.
const foo = 3;
export { foo };

10.6 Prefer default export for single-export modules

eslint: import/prefer-default-export Files that only ever export one thing are more readable and maintainable.
export default function foo() {}

10.7 Put all imports above non-import statements

eslint: import/first Since imports are hoisted, keeping them all at the top prevents surprising behavior.
import foo from 'foo';
import bar from 'bar';

foo.init();

10.8 Indent multiline imports

eslint: object-curly-newline Multiline imports should be indented just like multiline array and object literals. The curly braces follow the same indentation rules as every other curly brace block in the style guide.
import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE,
} from 'path';

10.9 Disallow Webpack loader syntax in imports

eslint: import/no-webpack-loader-syntax Using Webpack syntax in imports couples the code to a module bundler. Prefer using the loader syntax in webpack.config.js.
import fooSass from 'foo.scss';
import barCss from 'bar.css';

10.10 Do not include JavaScript file extensions

eslint: import/extensions Including extensions inhibits refactoring and inappropriately hardcodes implementation details of the module you’re importing in every consumer.
import foo from './foo';
import bar from './bar';
import baz from './baz';

Build docs developers (and LLMs) love