- 開発
READ MORE
AirbnbによるJavaScriptスタイルガイドです。
MITライセンスに基いて翻訳・公開いたします。
原文にはありませんが、項目ごとに目安となる分類を【】で示しました。
メモ: 本ガイドではBabelの利用を前提とします。また、babel-preset-airbnbあるいは同等のライブラリが必要です。他に、airbnb-browser-shimsまたは同等のライブラリでshims/polyfillsをアプリにインストールしていることも前提とします。
class
を使うこと。prototype
の直接操作は避けること。理由:
class
文法は簡潔かつわかりやすい。
// 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;
}
}
extends
を使うこと。
extends
はビルトイン機能であり、instanceof
を損なわずにプロトタイプ機能を継承できる。
// 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];
}
}
this
を返してもよい。// 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);
toString()
メソッドを作成してもよい。ただし正しく動作させ、副作用が生じないようにすること。class Jedi {
constructor(options = {}) {
this.name = options.name || 'no name';
}
getName() {
return this.name;
}
toString() {
return `Jedi - ${this.getName()}`;
}
}
no-useless-constructor
// 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';
}
}
no-dupe-class-members
理由: クラスのメンバー宣言が重複すると、最後の宣言が暗黙で優先される。重複はほとんどの場合バグの原因になる。
// Bad
class Foo {
bar() { return 1; }
bar() { return 2; }
}
// Good
class Foo {
bar() { return 1; }
}
// Good
class Foo {
bar() { return 2; }
}
import
とexport
で使い、標準でないモジュールシステムの利用は避けること。好みのモジュールシステムへのトランスパイルはいつでも行える。理由: モジュールは未来の機能だ。今のうちから未来の機能を使っておこう。
// 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;
import
でワイルドカードを使わないこと。理由: ワイルドカードを避けることで、デフォルトのexportが1つに確定する。
// Bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';
// Good
import AirbnbStyleGuide from './AirbnbStyleGuide';
import
から直接export
しないこと。理由: 一行に収まる方が簡潔ではあるが、
import
とexport
の方法を1つに定めて統一する方がよい。
// Bad
// filename es6.js
export { es6 as default } from './AirbnbStyleGuide';
// Good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;
import
は1つにまとめること。no-duplicate-imports
理由: 同じパスからの
import
が複数あるとコードのメンテナンスが困難になることがある。
// Bad
import foo from 'foo';
// … some other imports … //
import { named1, named2 } from 'foo';
// Good
import foo, { named1, named2 } from 'foo';
// Good
import foo, {
named1,
named2,
} from 'foo';
export
しないこと。import/no-mutable-exports
理由: ミューテーションは一般的に避けるべきであり、特にミュータブルなバインドの
export
は避けるべき。こうした特殊な手法が必要になることはなくもないが、一般には定数の参照だけをexport
すべき。
// Bad
let foo = 3;
export { foo };
// Good
const foo = 3;
export { foo };
export
でエクスポートする場合、名前付きexport
よりもdefault
のexport
が望ましい。import/prefer-default-export
// Bad
export function foo() {}
// Good
export default function foo() {}
import
文は他の文よりも上に置くこと。import/first
理由:
import
は巻き上げられる(ホイスティング)ので、常にトップに配置することで予想外の挙動を防げる。
// Bad
import foo from 'foo';
foo.init();
import bar from 'bar';
// Good
import foo from 'foo';
import bar from 'bar';
foo.init();
import
が複数行に渡る場合はインデントすること。インデント方法は配列やオブジェクトリテラルと同様にする。理由: 波かっこ
{ }
のスタイルを本スタイルガイドの他の波かっこブロックのルールに合わせるため。末尾のカンマ,
も省略しないこと。
// Bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';
// Good
import {
longNameA,
longNameB,
longNameC,
longNameD,
longNameE,
} from 'path';
import
文ではWebpackローダーの文法を使わないこと。import/no-webpack-loader-syntax
理由:
import
でWebpack文法を使うとモジュールバンドラー(module bundler)と結合される。Webpackのローダー文法はwebpack.config.js
で使うのが望ましい。
// Bad
import fooSass from 'css!sass!foo.scss';
import barCss from 'style!css!bar.css';
// Good
import fooSass from 'foo.scss';
import barCss from 'bar.css';
for-in
やfor-of
などのループではなく、JavaScriptの高階関数の利用が望ましい。no-iterator
no-restricted-syntax
本スタイルガイドでのイミュータブル重視ルールを守るため。値を返す純粋な関数を扱う方が、副作用を伴う方法よりも理解しやすい。
配列の列挙には
map()
、every()
、filter()
、find()
、findIndex()
、reduce()
、some()
などを使うこと。
配列の生成にObject.keys()
、Object.values()
、Object.entries()
を使うことでオブジェクトを列挙できるようになる。
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;
// ベスト(【TBD】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);
);
// ベスト(関数らしさを保っている)
const increasedByOne = numbers.map(num => num + 1);
理由: ES5へのトランスパイルがあまりうまくいっていない。
generator-star-spacing
理由:
function
と*
が合わさることで1つの概念キーワードが構成される。*
はfunction
の修飾子ではない。function*
は全体で1つであり、単なるfunction
とは異なる点に注意。
// Bad
function * foo() {
// ...
}
// Bad
const bar = function * () {
// ...
};
// Bad
const baz = function *() {
// ...
};
// Bad
const quux = function*() {
// ...
};
// Bad
function*foo() {
// ...
}
// Bad
function *foo() {
// ...
}
// 非常に悪い
function
*
foo() {
// ...
}
// 非常に悪い
const wat = function
*
() {
// ...
};
// Good
function* foo() {
// ...
}
// Good
const foo = function* () {
// ...
};
.
を使うこと。dot-notation
requireDotNotation
const luke = {
jedi: true,
age: 28,
};
// Bad
const isJedi = luke['jedi'];
// Good
const isJedi = luke.jedi;
[ ]
記法を使うこと。const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
const
またはlet
で行うこと。このとおりにしない変数はグローバルになってしまう。キャプテン・プラネットで警告されているように、私たちはグローバル名前空間の汚染を避けたいと考えている。no-undef
prefer-const
// Bad
superPower = new SuperPower();
// Good
const superPower = new SuperPower();
const
やlet
は変数宣言ごとに付けること(まとめて書かない)。one-var
jscs: disallowMultipleVarDecl
理由: 変数宣言の書式が揃うので追加が楽になるうえ、末尾の
;
と,
の取り違えを心配しないで済む。そうしないと約物レベルのわかりにくい違いが生じてしまう。デバッガでも宣言ごとにステップ実行されるのもよい。まとめて宣言するとデバッガでも1ステップで終わってしまい、宣言ごとにチェックしづらくなる。
// Bad
const items = getItems(),
goSportsTeam = true,
dragonball = 'z';
// Bad
// (上との違いに注意: こういう間違いが起きやすくなる)
const items = getItems(),
goSportsTeam = true;
dragonball = 'z';
// Good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';
const
行を先に書き、次にlet
行を書くこと(両者を混ぜて書かない)。理由: 先に行った代入に依存する代入を追加するときに便利。
// Bad
let i, len, dragonball,
items = getItems(),
goSportsTeam = true;
// Bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;
// Good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
const
やlet
はブロックスコープであり、関数スコープではない。
// Bad - 不要な関数呼び出し
function checkName(hasName) {
const name = getName();
if (hasName === 'test') {
return false;
}
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
// Good
function checkName(hasName) {
if (hasName === 'test') {
return false;
}
const name = getName();
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
理由: 変数代入をチェインすると、暗黙でグローバル変数が作成される。
// Bad
(function example() {
// JavaScriptでは以下のように解釈される:
// let a = ( b = ( c = 1 ) );
// letキーワードは変数aにしか効かない
// 変数bと変数cはグローバル変数になる
let a = b = c = 1;
}());
console.log(a); // ReferenceErrorをスロー
console.log(b); // 1
console.log(c); // 1
// Good
(function example() {
let a = 1;
let b = a;
let c = a;
}());
console.log(a); // ReferenceErrorをスロー
console.log(b); // ReferenceErrorをスロー
console.log(c); // ReferenceErrorをスロー
// `const`でも同様の点に注意すること
++
や--
は避けること。no-plusplus
理由: eslintのドキュメントにあるとおり、単項の
++
や--
にはセミコロン;
が自動挿入されやすいため、これで値を増減させるとアプリでひそかにエラーが発生することがある。
また、num++
やnum ++
よりもnum += 1
などの文の方が値を改変していることが明確になる。単項の++
や--
による増減を排除することで、プログラムで思わぬ挙動の原因になる「値の事前インクリメント/デクリメント問題」も防止できる。
// Bad
const array = [1, 2, 3];
let num = 1;
num++;
--num;
let sum = 0;
let truthyCount = 0;
for (let i = 0; i < array.length; i++) {
let value = array[i];
sum += value;
if (value) {
truthyCount++;
}
}
// Good
const array = [1, 2, 3];
let num = 1;
num += 1;
num -= 1;
const sum = array.reduce((a, b) => a + b, 0);
const truthyCount = array.filter(Boolean).length;
var
宣言はスコープの先頭へ巻き上げられるが、宣言した変数の代入は巻き上げられない点に注意。一方、const
宣言とlet
宣言はTDZ(Temporal Dead Zones)という新しい概念の恩恵を受ける。「typeofはもはや安全ではない」を理解することが重要である。// notDefinedというグローバル変数は定義されていないことが前提
// 当然これは動かない
function example() {
console.log(notDefined); // => ReferenceErrorをスロー
}
// 変数参照の後で変数宣言を作成すると
// 変数が巻き上げられるため、これは動作する
// 注意: 代入の値`true`は巻き上げられない
function example() {
console.log(declaredButNotAssigned); // => undefined
var declaredButNotAssigned = true;
}
// インタプリタは変数宣言をスコープの冒頭に巻き上げられる
// つまりコード例は次のように書き換えられる
function example() {
let declaredButNotAssigned;
console.log(declaredButNotAssigned); // => undefined
declaredButNotAssigned = true;
}
// constやletを使う場合
function example() {
console.log(declaredButNotAssigned); // => ReferenceErrorをスロー
console.log(typeof declaredButNotAssigned); // => ReferenceErrorをスロー
const declaredButNotAssigned = true;
}
function example() {
console.log(anonymous); // => undefined
anonymous(); // => TypeError anonymous is not a function
var anonymous = function () {
console.log('無名関数式');
};
}
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
superPower(); // => ReferenceError superPower is not defined
var named = function superPower() {
console.log('とびます');
};
}
// 関数名と変数名が同じ場合も同様
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
var named = function named() {
console.log('named');
};
}
function example() {
superPower(); // => とびます
function superPower() {
console.log('とびます');
}
}