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;
}
}
function Queue(contents = []) {
this.queue = [...contents];
}
Queue.prototype.pop = function () {
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];
}
}
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function () {
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);
Jedi.prototype.jump = function () {
this.jumping = true;
return true;
};
Jedi.prototype.setHeight = function (height) {
this.height = height;
};
const luke = new Jedi();
luke.jump(); // => true
luke.setHeight(20); // => undefined
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';
}
}
// bad - empty constructor
class Jedi {
constructor() {}
getName() {
return this.name;
}
}
// bad - only delegates to parent
class Rey extends Jedi {
constructor(...args) {
super(...args);
}
}
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; }
}
class Foo {
bar() { return 1; }
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');
}
}
class Foo {
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;
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;
10.2 Do not use wildcard imports
This ensures you have a single default export.
import AirbnbStyleGuide from './AirbnbStyleGuide';
import * as 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;
// filename es6.js
export { es6 as default } from './AirbnbStyleGuide';
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';
import foo from 'foo';
// … some other imports … //
import { 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 };
let 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();
import foo from 'foo';
foo.init();
import bar from 'bar';
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';
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';
import fooSass from 'css!sass!foo.scss';
import barCss from 'style!css!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';
import foo from './foo.js';
import bar from './bar.jsx';
import baz from './baz/index.jsx';