- 1〜8: 型、参照、オブジェクト、配列、関数ほか
- 9〜14: クラス、モジュール、イテレータ、プロパティ、変数、巻き上げ -- 本記事
- 15〜26: 比較演算子、ブロック、制御文、型変換、命名規則ほか
概要
AirbnbによるJavaScriptスタイルガイドです。
MITライセンスに基いて翻訳・公開いたします。
- 英語スタイルガイド: airbnb/javascript: README.md
- 更新日: 2017/09/08
- 著者: Airbnb
凡例
原文にはありませんが、項目ごとに目安となる分類を【】で示しました。
- 【必須】【禁止】:従わないと技術的な悪影響が生じる
- 【推奨】【非推奨】:技術上の理由から強く推奨される、または推奨されない
- 【選択】:採用してもしなくてもよいスタイル
- 【スタイル】:読みやすさのためのスタイル統一指示
- 【知識】:指示に該当しない基礎知識
JavaScriptスタイルガイド 9〜14: クラス、モジュール、イテレータ、プロパティ、変数、巻き上げ (翻訳)
メモ: 本ガイドではBabelの利用を前提とします。また、babel-preset-airbnbあるいは同等のライブラリが必要です。他に、airbnb-browser-shimsまたは同等のライブラリでshims/polyfillsをアプリにインストールしていることも前提とします。
9. クラス(class)とコンストラクタ(constructor)
9.1 【推奨】常に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;
}
}
9.2 【推奨】継承には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];
}
}
9.3 【選択】メソッドは、メソッドチェーンの目的で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);
9.4 【選択】独自のtoString()
メソッドを作成してもよい。ただし正しく動作させ、副作用が生じないようにすること。
class Jedi {
constructor(options = {}) {
this.name = options.name || 'no name';
}
getName() {
return this.name;
}
toString() {
return `Jedi - ${this.getName()}`;
}
}
9.5 【知識】クラスには、指定のない場合にデフォルトのコンストラクタが1つ与えられる。空のコンストラクタ関数や、親クラスに委譲するだけのためのコンストラクタ関数は不要。
- eslint:
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';
}
}
9.6 【禁止】クラスのメンバーの重複は避ける。
- eslint:
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; }
}
10. モジュール(module)
10.1 【推奨】モジュールは常に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;
10.2 【非推奨】import
でワイルドカードを使わないこと。
理由: ワイルドカードを避けることで、デフォルトのexportが1つに確定する。
// Bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';
// Good
import AirbnbStyleGuide from './AirbnbStyleGuide';
10.3 【スタイル】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;
10.4 【推奨】1つのパスからのimport
は1つにまとめること。
- eslint:
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';
10.5 【必須】ミュータブルなバインドをexport
しないこと。
- eslint:
import/no-mutable-exports
理由: ミューテーションは一般的に避けるべきであり、特にミュータブルなバインドの
export
は避けるべき。こうした特殊な手法が必要になることはなくもないが、一般には定数の参照だけをexport
すべき。
// Bad
let foo = 3;
export { foo };
// Good
const foo = 3;
export { foo };
10.6 【推奨】複数のモジュールを1つのexport
でエクスポートする場合、名前付きexport
よりもdefault
のexport
が望ましい。
- eslint:
import/prefer-default-export
// Bad
export function foo() {}
// Good
export default function foo() {}
10.7 【必須】import
文は他の文よりも上に置くこと。
- eslint:
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();
10.8 【スタイル】import
が複数行に渡る場合はインデントすること。インデント方法は配列やオブジェクトリテラルと同様にする。
理由: 波かっこ
{ }
のスタイルを本スタイルガイドの他の波かっこブロックのルールに合わせるため。末尾のカンマ,
も省略しないこと。
// Bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';
// Good
import {
longNameA,
longNameB,
longNameC,
longNameD,
longNameE,
} from 'path';
10.9 【禁止】モジュールのimport
文ではWebpackローダーの文法を使わないこと。
- eslint:
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';
11. イテレータ(iterator)とジェネレータ(generator)
11.1 【非推奨】イテレータを使ってはならない。for-in
やfor-of
などのループではなく、JavaScriptの高階関数の利用が望ましい。
- eslint:
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);
11.2 【非推奨】ジェネレータは当面使わないこと。
理由: ES5へのトランスパイルがあまりうまくいっていない。
11.3 【必須】ジェネレータの利用が避けられない場合や、上述のアドバイスに従うつもりがない場合は、ジェネレータの関数のシグネチャでのスペーシングを正しく行うこと。
- eslint:
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* () {
// ...
};
12. プロパティ(property)
12.1 【推奨】プロパティへのアクセスにはドット記法.
を使うこと。
- eslint:
dot-notation
- jscs:
requireDotNotation
const luke = {
jedi: true,
age: 28,
};
// Bad
const isJedi = luke['jedi'];
// Good
const isJedi = luke.jedi;
12.2 【推奨】プロパティに変数でアクセスする場合は角かっこ[ ]
記法を使うこと。
const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
13. 変数(variable)
13.1 【必須】変数宣言は常にconst
またはlet
で行うこと。このとおりにしない変数はグローバルになってしまう。キャプテン・プラネットで警告されているように、私たちはグローバル名前空間の汚染を避けたいと考えている。
- eslint:
no-undef
prefer-const
// Bad
superPower = new SuperPower();
// Good
const superPower = new SuperPower();
13.2 【推奨】const
やlet
は変数宣言ごとに付けること(まとめて書かない)。
- eslint:
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';
13.3 【スタイル】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;
13.4 【必須】変数の代入は、意味のある場所で行うこと。
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;
}
13.5 【禁止】変数代入をチェイン(連鎖代入)しないこと。
理由: 変数代入をチェインすると、暗黙でグローバル変数が作成される。
// 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`でも同様の点に注意すること
13.6 【非推奨】単項の++
や--
は避けること。
- eslint
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;
14. 巻き上げ(ホイスティング: hoisting)
14.1 【知識】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;
}
14.2 【知識】無名関数式では変数名が巻き上げられるが、関数の代入は巻き上げられない点に注意。
function example() {
console.log(anonymous); // => undefined
anonymous(); // => TypeError anonymous is not a function
var anonymous = function () {
console.log('無名関数式');
};
}
14.3 【知識】名前付き関数式では変数名が巻き上げられるが、関数名と関数本体は巻き上げられない点に注意。
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');
};
}
14.4 【知識】関数宣言では、関数名と関数本体が巻き上げられる点に注意。
function example() {
superPower(); // => とびます
function superPower() {
console.log('とびます');
}
}
- 詳しくはJavaScript Scoping & Hoisting(Ben Cherry)を参照。
- 1〜8: 型、参照、オブジェクト、配列、関数ほか
- 9〜14: クラス、モジュール、イテレータ、プロパティ、変数、巻き上げ -- 本記事
- 15〜26: 比較演算子、ブロック、制御文、型変換、命名規則ほか