JavaScript-Style-Guide

JavaScript风格指南摘抄(自己尚不熟悉的)

https://github.com/airbnb/javascript

https://github.com/lin-123/javascript

Objects

one place

Use computed property names when creating objects with dynamic property names.

Why? They allow you to define all the properties of an object in one place.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getKey(k) {
return `a key named ${k}`;
}

// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};

group

Group your shorthand properties at the beginning of your object declaration.

Why? It’s easier to tell which properties are using the shorthand.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';

// bad
const obj = {
episodeOne: 1,
twoJediWalkIntoACantina: 2,
lukeSkywalker,
episodeThree: 3,
mayTheFourth: 4,
anakinSkywalker,
};

// good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};

quote-props

Only quote properties that are invalid identifiers. eslint: quote-props

Why? In general we consider it subjectively easier to read. It improves syntax highlighting, and is also more easily optimized by many JS engines.

1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5,
};

// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5,
};

no-prototype-builtins

Do not call Object.prototype methods directly, such as hasOwnProperty, propertyIsEnumerable, and isPrototypeOf. eslint: no-prototype-builtins

Why? These methods may be shadowed by properties on the object in question - consider { hasOwnProperty: false } - or, the object may be a null object (Object.create(null)).

1
2
3
4
5
6
7
8
9
10
11
12
// bad
console.log(object.hasOwnProperty(key));

// good
console.log(Object.prototype.hasOwnProperty.call(object, key));

// best
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
console.log(has.call(object, key));
/* or */
import has from 'has'; // https://www.npmjs.com/package/has
console.log(has(object, key));

shallow-copy

Prefer the object spread operator over Object.assign to shallow-copy objects. Use the object rest operator to get a new object with certain properties omitted.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

Arrays

copy arrays

Use array spreads ... to copy arrays.

1
2
3
4
5
6
7
8
9
10
11
// bad
const len = items.length;
const itemsCopy = [];
let i;

for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}

// good
const itemsCopy = [...items];

convert an iterable object

To convert an iterable object to an array, use spreads ... instead of Array.from.

1
2
3
4
5
6
7
const foo = document.querySelectorAll('.foo');

// good
const nodes = Array.from(foo);

// best
const nodes = [...foo];

convert an array-like object

Use Array.from for converting an array-like object to an array.

1
2
3
4
5
6
7
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };

// bad
const arr = Array.prototype.slice.call(arrLike);

// good
const arr = Array.from(arrLike);

mapping over iterables

Use Array.from instead of spread ... for mapping over iterables, because it avoids creating an intermediate array.

1
2
3
4
5
// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar);

array-callback-return

Use return statements in array method callbacks. It’s ok to omit the return if the function body consists of a single statement returning an expression without side effects, following 8.2. eslint: array-callback-return

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});

// good
[1, 2, 3].map((x) => x + 1);

// bad - no returned value means `acc` becomes undefined after the first iteration
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
const flatten = acc.concat(item);
});

// good
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
const flatten = acc.concat(item);
return flatten;
});

// bad
inbox.filter((msg) => {
const { subject, author } = msg;
if (subject === 'Mockingbird') {
return author === 'Harper Lee';
} else {
return false;
}
});

// good
inbox.filter((msg) => {
const { subject, author } = msg;
if (subject === 'Mockingbird') {
return author === 'Harper Lee';
}

return false;
});

Destructuring

object destructuring

Use object destructuring when accessing and using multiple properties of an object. eslint: prefer-destructuring

Why? Destructuring saves you from creating temporary references for those properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;

return `${firstName} ${lastName}`;
}

// good
function getFullName(user) {
const { firstName, lastName } = user;
return `${firstName} ${lastName}`;
}

// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}

array destructuring

Use array destructuring. eslint: prefer-destructuring

1
2
3
4
5
6
7
8
const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;

multiple return values

Use object destructuring for multiple return values, not array destructuring.

Why? You can add new properties over time or change the order of things without breaking call sites.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// bad
function processInput(input) {
// then a miracle occurs
return [left, right, top, bottom];
}

// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);

// good
function processInput(input) {
// then a miracle occurs
return { left, right, top, bottom };
}

// the caller selects only the data they need
const { left, top } = processInput(input);

Function

statements

ECMA-262 defines a block as a list of statements. A function declaration is not a statement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}

// good
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}

arguments

Never use arguments, opt to use rest syntax ... instead. eslint: prefer-rest-params

Why? ... is explicit about which arguments you want pulled. Plus, rest arguments are a real Array, and not merely Array-like like arguments.

1
2
3
4
5
6
7
8
9
10
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}

// good
function concatenateAll(...args) {
return args.join('');
}

default parameter syntax

Use default parameter syntax rather than mutating function arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// really bad
function handleThings(opts) {
// No! We shouldn’t mutate function arguments.
// Double bad: if opts is falsy it'll be set to an object which may
// be what you want but it can introduce subtle bugs.
opts = opts || {};
// ...
}

// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}

// good
function handleThings(opts = {}) {
// ...
}

default parameters last

Always put default parameters last.

1
2
3
4
5
6
7
8
9
// bad
function handleThings(opts = {}, name) {
// ...
}

// good
function handleThings(name, opts = {}) {
// ...
}

Never mutate parameters

Never mutate parameters. eslint: no-param-reassign

Why? Manipulating objects passed in as parameters can cause unwanted variable side effects in the original caller.

1
2
3
4
5
6
7
8
9
// bad
function f1(obj) {
obj.key = 1;
}

// good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}

Never reassign parameters

Never reassign parameters. eslint: no-param-reassign

Why? Reassigning parameters can lead to unexpected behavior, especially when accessing the arguments object. It can also cause optimization issues, especially in V8.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// bad
function f1(a) {
a = 1;
// ...
}

function f2(a) {
if (!a) { a = 1; }
// ...
}

// good
function f3(a) {
const b = a || 1;
// ...
}

function f4(a = 1) {
// ...
}

Arrow Function

wrap it in parentheses

In case the expression spans over multiple lines, wrap it in parentheses for better readability.

Why? It shows clearly where the function starts and ends.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bad
['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
);

// good
['get', 'post', 'put'].map((httpMethod) => (
Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
));

Classes & Constructors

Always use class

Always use class. Avoid manipulating prototype directly.

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

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

// good
class Queue {
constructor(contents = []) {
this.queue = [...contents];
}
pop() {
const value = this.queue[0];
this.queue.splice(0, 1);
return value;
}
}

Use extends for inheritance

Use extends for inheritance.

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

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

// good
class PeekableQueue extends Queue {
peek() {
return this.queue[0];
}
}

method chaining

Methods can return this to help with method chaining.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// bad
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

// good
class Jedi {
jump() {
this.jumping = true;
return this;
}

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

const luke = new Jedi();

luke.jump()
.setHeight(20);

no-useless-constructor

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// bad
class Jedi {
constructor() {}

getName() {
return this.name;
}
}

// bad
class Rey extends Jedi {
constructor(...args) {
super(...args);
}
}

// good
class Rey extends Jedi {
constructor(...args) {
super(...args);
this.name = 'Rey';
}
}

Modules

use modules (import/export)

Always use modules (import/export) over a non-standard module system. You can always transpile to your preferred module system.

Why? Modules are the future, let’s start using the future now.

1
2
3
4
5
6
7
8
9
10
11
// bad
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;

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

// best
import { es6 } from './AirbnbStyleGuide';
export default es6;

not use wildcard imports

Do not use wildcard imports.

Why? This makes sure you have a single default export.

1
2
3
4
5
// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';

// good
import AirbnbStyleGuide from './AirbnbStyleGuide';

do not directly

And do not export directly from an import.

Why? Although the one-liner is concise, having one clear way to import and one clear way to export makes things consistent.

1
2
3
4
5
6
7
8
// bad
// filename es6.js
export { es6 as default } from './AirbnbStyleGuide';

// good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;

import/no-mutable-exports

Do not export mutable bindings. eslint: import/no-mutable-exports

Why? Mutation should be avoided in general, but in particular when exporting mutable bindings. While this technique may be needed for some special cases, in general, only constant references should be exported.

1
2
3
4
5
6
7
// bad
let foo = 3;
export { foo };

// good
const foo = 3;
export { foo };

import/prefer-default-export

In modules with a single export, prefer default export over named export. eslint: import/prefer-default-export

Why? To encourage more files that only ever export one thing, which is better for readability and maintainability.

1
2
3
4
5
// bad
export function foo() {}

// good
export default function foo() {}

Iterators and Generators

Prefer higher-order functions

Don’t use iterators. Prefer JavaScript’s higher-order functions instead of loops like for-in or for-of. eslint: no-iterator no-restricted-syntax

Why? This enforces our immutable rule. Dealing with pure functions that return values is easier to reason about than side effects.

Use map() / every() / filter() / find() / findIndex() / reduce() / some() / … to iterate over arrays, and Object.keys() / Object.values() / Object.entries() to produce arrays so you can iterate over objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
sum += num;
}
sum === 15;

// good
let sum = 0;
numbers.forEach((num) => {
sum += num;
});
sum === 15;

// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;

// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
increasedByOne.push(numbers[i] + 1);
}

// good
const increasedByOne = [];
numbers.forEach((num) => {
increasedByOne.push(num + 1);
});

// best (keeping it functional)
const increasedByOne = numbers.map((num) => num + 1);

generator-star-spacing

If you must use generators, or if you disregard our advice, make sure their function signature is spaced properly. eslint: generator-star-spacing

Why? function and * are part of the same conceptual keyword - * is not a modifier for function, function*is a unique construct, different from function.

1
2
3
4
5
6
7
8
9
// good
function* foo() {
// ...
}

// good
const foo = function* () {
// ...
};

Properties

dot-notation

Use dot notation when accessing properties. eslint: dot-notation

1
2
3
4
5
6
7
8
9
10
const luke = {
jedi: true,
age: 28,
};

// bad
const isJedi = luke['jedi'];

// good
const isJedi = luke.jedi;

bracket notation

Use bracket notation [] when accessing properties with a variable.

1
2
3
4
5
6
7
8
9
10
const luke = {
jedi: true,
age: 28,
};

function getProp(prop) {
return luke[prop];
}

const isJedi = getProp('jedi');

exponentiation operator

Use exponentiation operator ** when calculating exponentiations. eslint: no-restricted-properties.

1
2
3
4
5
// bad
const binary = Math.pow(2, 10);

// good
const binary = 2 ** 10;

Variables

no-multi-assign

Don’t chain variable assignments. eslint: no-multi-assign

Why? Chaining variable assignments creates implicit global variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// bad
(function example() {
// JavaScript interprets this as
// let a = ( b = ( c = 1 ) );
// The let keyword only applies to variable a; variables b and c become
// global variables.
let a = b = c = 1;
}());

console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1

// good
(function example() {
let a = 1;
let b = a;
let c = a;
}());

console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError

// the same applies for `const`

Comparison Operators & Equality

shortcuts for

Use shortcuts for booleans, but explicit comparisons for strings and numbers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// bad
if (isValid === true) {
// ...
}

// good
if (isValid) {
// ...
}

// bad
if (name) {
// ...
}

// good
if (name !== '') {
// ...
}

// bad
if (collection.length) {
// ...
}

// good
if (collection.length > 0) {
// ...
}

Commas

Additional trailing comma

Additional trailing comma: Yup. eslint: comma-dangle

Why? This leads to cleaner git diffs. Also, transpilers like Babel will remove the additional trailing comma in the transpiled code which means you don’t have to worry about the trailing comma problem in legacy browsers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// bad - git diff without trailing comma
const hero = {
firstName: 'Florence',
- lastName: 'Nightingale'
+ lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing']
};

// good - git diff with trailing comma
const hero = {
firstName: 'Florence',
lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing'],
};

// bad
function createHero(
firstName,
lastName,
inventorOf
) {
// does nothing
}

// good
function createHero(
firstName,
lastName,
inventorOf,
) {
// does nothing
}

// good (note that a comma must not appear after a "rest" element)
function createHero(
firstName,
lastName,
inventorOf,
...heroArgs
) {
// does nothing
}

// bad
createHero(
firstName,
lastName,
inventorOf
);

// good
createHero(
firstName,
lastName,
inventorOf,
);

// good (note that a comma must not appear after a "rest" element)
createHero(
firstName,
lastName,
inventorOf,
...heroArgs
)

Type Casting & Coercion

Strings

Strings: eslint: no-new-wrappers

1
2
3
4
5
6
7
8
9
10
11
12
13
// => this.reviewScore = 9;

// bad
const totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string"

// bad
const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf()

// bad
const totalScore = this.reviewScore.toString(); // 不保证返回string

// good
const totalScore = String(this.reviewScore);

Numbers

Numbers: Use Number for type casting and parseInt always with a radix for parsing strings. eslint: radix no-new-wrappers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const inputValue = '4';

// bad
const val = new Number(inputValue);

// bad
const val = +inputValue;

// bad
const val = inputValue >> 0;

// bad
const val = parseInt(inputValue);

// good
const val = Number(inputValue);

// good
const val = parseInt(inputValue, 10);

Booleans

Booleans: eslint: no-new-wrappers

1
2
3
4
5
6
7
8
9
10
const age = 0;

// bad
const hasAge = new Boolean(age);

// good
const hasAge = Boolean(age);

// best
const hasAge = !!age;

Naming Conventions

Use camelCase

Use camelCase when you export-default a function. Your filename should be identical to your function’s name.

1
2
3
4
5
function makeStyleGuide() {
// ...
}

export default makeStyleGuide;

Use PascalCase

Use PascalCase when you export a constructor / class / singleton / function library / bare object.

1
2
3
4
5
6
const AirbnbStyleGuide = {
es6: {
},
};

export default AirbnbStyleGuide;