eslint、コーディング規約を勉強しており、アウトプットを兼ねてAirbnb JavaScript Style Guideを翻訳してみることにした。翻訳は筆者の独断であるため情報が常に最新かつ正確であるとは限らない。以下の公式ドキュメントを元に翻訳を行なっている。サンプルコードも公式ドキュメントを引用している。
https://github.com/airbnb/javascript
ルール
2. 参照(References)
2.1: すべての参照にはconstを使用し、varの使用は避ける。
- ESLintルール:
prefer-const
,no-const-assign
// bad
var a = 1;
// good
const a = 1;
2.2: 参照を再代入する必要がある場合は、varの代わりにletを使用する。
- ESLintルール:
no-var
// bad
var count = 1;
if (true) {
count += 1;
}
// good
let count = 1;
if (true) {
count += 1;
}
2.3: letとconst は共にブロックスコープ、var
は関数スコープであることに注意する。
{
let a = 1;
const b = 1;
var c = 1;
}
console.log(a); // ReferenceError(ブロックスコープ外から参照できない)
console.log(b); // ReferenceError(ブロックスコープ外から参照できない)
console.log(c); // Prints 1(ブロックスコープ外から参照できる)
3. オブジェクト(Objects)
3.1: オブジェクトを作成する際には、リテラル構文を使用する。
- ESLintルール:
no-new-object
// bad
const item = new Object();
// good
const item = {}
3.2: 動的なプロパティ名でオブジェクトを作成する際には、計算されたプロパティ名を使用する。
3.3: オブジェクトメソッドの省略記法を使用する。
- ESLintルール:
object-shorthand
// bad
const atom = {
value: 1,
addValue: function(value) {
return atom.value + value
}
}
// good
const atom = {
value: 1,
addValue(value) {
return atom.value + value
}
}
3.4: プロパティ値の省略記法を使用する。
- ESLintルール:
object-shorthand
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
lukeSkywalker: lukeSkywalker,
};
// good
const obj = {
lukeSkywalker,
};
3.5: 省略形のプロパティは、オブジェクト宣言の最初にまとめる。
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,
};
3.6: 無効な識別子であるプロパティだけをクォートする。
- ESLintルール:
quote-props
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5,
};
// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5,
};
3.7: Object.prototypeのメソッド(例:hasOwnProperty、propertyIsEnumerable、isPrototypeOf)を直接呼び出さない。
- ESLintルール:
no-prototype-builtins
// bad
console.log(object.hasOwnProperty(key));
// good
console.log(Object.prototype.hasOwnProperty.call(object, key));
3.8: オブジェクトを浅くコピーする場合は、Object.assignよりもオブジェクトスプレッド構文を推奨する。特定のプロパティを除外した新しいオブジェクトを得るためには、オブジェクトの残りのパラメータ構文を使用する。
- ESLintルール:
prefer-object-spread
// 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 }
4. 配列(Arrays)
4.1 配列を作成する際には、リテラル構文を使用する。
- ESLintルール:
no-array-constructor
// bad
const items = new Array();
// good
const items = [];
4.2 配列に要素を追加する際には、直接の代入ではなくArray#pushを使用する。
const someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
4.3 配列をコピーする場合には、スプレッド演算子…を使用する。
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
4.4 反復可能なオブジェクトを配列に変換する場合には、Array.fromではなくスプレッド演算子…を使用する。
const foo = document.querySelectorAll('.foo');
// good
const nodes = Array.from(foo);
// best
const nodes = [...foo];
4.5 配列のようなオブジェクトを配列に変換する場合には、Array.fromを使用する。
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };
// bad
const arr = Array.prototype.slice.call(arrLike); // ['foo', 'bar', 'baz']
// good
const arr = Array.from(arrLike); // ['foo', 'bar', 'baz']
4.6 反復可能なオブジェクトに対してマッピングを行う場合、中間配列を作成しないようにArray.fromをスプレッド演算子…の代わりに使用する。
// bad
const baz = [...foo].map(bar);
// good
const baz = Array.from(foo, bar);
4.7 配列メソッドのコールバック内でreturn文を使用する。関数本体が副作用のない単一の式を返す単一の文から成る場合、returnを省略してもOK。これは規則8.2に従う。
- ESLintルール:
array-callback-return
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map((x) => x + 1);
// 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;
});
4.8 配列が複数行にわたる場合、配列の開始括弧の後と閉じ括弧の前に改行を使用する。
// bad
const arr = [
[0, 1], [2, 3], [4, 5],
];
// good
const arr = [[0, 1], [2, 3], [4, 5]];
// bad
const objectInArray = [{
id: 1,
}, {
id: 2,
}];
// good
const objectInArray = [
{
id: 1,
},
{
id: 2,
},
];
// bad
const numberInArray = [
1, 2,
];
// good
const numberInArray = [
1,
2,
];
5. 分割代入(Destructuring)
5.1 オブジェクトの複数のプロパティにアクセスして使用する場合は、オブジェクトの分割代入を使用する。
- ESLintルール:
prefer-destructuring
- 理由: 分割代入を用いることで、そのプロパティに対する一時的な参照を作成する手間や、オブジェクトへの繰り返しアクセスを減らすことができる。オブジェクトへの繰り返しアクセスは、コードが繰り返され、読む量が増え、ミスをする機会も増える。また、オブジェクトの分割代入は、使用するオブジェクトの構造をブロック内で一箇所にまとめて定義できるため、ブロック全体を読まなくても何が使用されているのかが分かる。
// 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}`;
}
5.2 配列の分割代入を使用する。
- ESLintルール:
prefer-destructuring
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
5.3 複数の戻り値に対しては、配列の分割代入ではなく、オブジェクトの分割代入を使用する。
// 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);
6. 文字列(Strings)
6.1 文字列にはシングルクォート(''
)を使用する。
- ESLintルール: quotes
// bad
const name = "Capt. Janeway";
// bad - テンプレート リテラルには補間または改行が含まれている必要がある。
const name = `Capt. Janeway`;
// good
const name = 'Capt. Janeway';
6.2 100文字を超える行になるような文字列は、文字列の連結を用いて複数行にわたって記述しない。
- 理由: 分割された文字列は扱いにくく、コードが検索しにくくなる。
// bad
const errorMessage = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.';
// bad
const errorMessage = 'This is a super long error that was thrown because ' +
'of Batman. When you stop to think about how Batman had anything to do ' +
'with this, you would get nowhere fast.';
// good
const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';
6.3 文字列をプログラムで動的に生成する場合は、連結ではなくテンプレート文字列を使用する。
- ESLintルール:
prefer-templat
e,template-curly-spacing
- 理由: テンプレート文字列は、適切な改行と文字列補間の機能を持つ、読みやすく簡潔な構文を提供する。
// bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// bad
function sayHi(name) {
return `How are you, ${ name }?`;
}
// good
function sayHi(name) {
return `How are you, ${name}?`;
}
6.4 文字列にeval()
を使用しない。これは多くの脆弱性を生み出す。
- ESLintルール:
no-eval
6.5 文字列内で不必要にエスケープ文字を使用しない
- ESLintルール:
no-useless-escape
- 理由: バックスラッシュは可読性を損なうため、必要な場合にのみ存在すべきである。
// bad
const foo = '\'this\' \i\s \"quoted\"';
// good
const foo = '\'this\' is "quoted"';
const foo = `my name is '${name}'`;
7. 関数(Functions)
7.1 名前付き関数式を関数宣言の代わりに使用する。
- ESLintルール:
func-style
,func-names
- 理由: 関数宣言は巻き上げ(hoisting)されるため、ファイル内で定義される前に関数を簡単に参照できてしまう。これは可読性と保守性に悪影響を与える。式に名前を明示的に付けることを忘れない。これにより、エラーのコールスタックについて行われる任意の仮定が排除される。
// bad
function foo() {
// ...
}
// bad
const foo = function () {
// ...
};
// good
// 変数参照の呼び出しと区別される字句名
const short = function longUniqueMoreDescriptiveLexicalFoo() {
// ...
};
7.2 即時実行関数式(IIFE)は括弧で囲む。
- ESLintルール:
wrap-iife
- 理由: 即時実行関数式は一つの単位である。括弧で囲むことで、その一体性が明確に表現される。だが、モジュールが広く使われている今日では、IIFEはほとんど必要ない。
// 即時呼び出し関数式 (IIFE)
(function () {
console.log('Welcome to the Internet. Please follow me.');
}());
7.3 非関数ブロック(if, whileなど)内で関数を宣言しない。代わりに関数を変数に割り当てる。
- ESLintルール:
no-loop-func
- 理由: ブラウザはそれぞれが異なる方法で解釈するため、問題が生じる可能性があるため。
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
7.5 パラメータ名にargumentsを使用しない。これが関数スコープに与えられるargumentsオブジェクトよりも優先される。
// bad
function foo(name, options, arguments) {
// ...
}
// good
function foo(name, options, args) {
// ...
}
argumentsとは何なのか?
JavaScriptにおいて、arguments
オブジェクトは特別な種類の配列風オブジェクトで、関数内からアクセスすることができる。このオブジェクトには、関数に渡されたすべての引数が格納されている。arguments
オブジェクトは主に可変数の引数を持つ関数で使用される。
function myFunction() {
console.log(arguments[0]); // 第1引数
console.log(arguments[1]); // 第2引数
console.log(arguments[2]); // 第3引数
// ...
}
myFunction('a', 'b', 'c'); // 出力: a, b,
近年では、arguments
の代わりにES6(ECMAScript 2015)で導入されたレスト構文(...args
)がよく使用される(7.6参照)。この構文を使用すると、関数に渡された引数を配列として扱うことができる。
function myFunction(...args) {
console.log(args[0]); // 第1引数
console.log(args[1]); // 第2引数
console.log(args[2]); // 第3引数
}
myFunction('a', 'b', 'c'); // 出力: a, b, c
JavaScriptの関数には、関数定義に指定されていない引数を呼び出し時に渡すことができる。このような追加された引数は、arguments
オブジェクト内で参照することができるのだ。
つまり、上記のmyFunction
が引数を取らないように定義されていても、関数が呼び出される際には任意の数の引数を渡すことができる。この際、arguments[0]
は最初の引数(この例では'a'
)を指し、arguments[1]
は次の引数(この例では'b'
)を指す、といったように動作する。
この動作はJavaScriptが持つ柔軟性の一例であり、その特性を利用して可変長の引数を処理することが可能。ただし、この特性は時に読みにくいコードを生む可能性もある。
7.6 argumentsを使用しないで、代わりにレスト構文…を使用する。
- ESLintルール:
prefer-rest-params
- 理由: …は、取得したい引数を明示的に示す。また、argumentsとは異なり、レスト引数は真の配列である。
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
7.7 関数引数を変更するのではなく、デフォルトパラメータ構文を使用する。
// 最悪
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 || {};
// ...
}
// 悪い
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
7.8 デフォルトパラメータで副作用を避ける。
let b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
7.9 デフォルトパラメータは常に最後に配置する。
- ESLintルール:
default-param-last
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
7.10 新しい関数を作成するためにFunctionコンストラクタを使用しない。
- ESLintルール:
no-new-func
- 理由: この方法で関数を作成すると、文字列がeval()のように評価され、セキュリティ上の脆弱性が生じるため。
// bad
const add = new Function('a', 'b', 'return a + b');
// still bad
const subtract = Function('a', 'b', 'return a - b');
7.11 関数シグネチャでのスペーシング。
- ESLintルール:
space-before-function-paren
,space-before-blocks
- 理由: 一貫性が重要であり、名前を追加または削除する際にスペースを追加または削除する必要がないため。
// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const x = function () {};
const y = function a() {};
7.12 パラメータを変更しない。
- ESLintルール:
no-param-reassign
- 理由: パラメータとして渡されたオブジェクトを変更すると、呼び出し元で望ましくない副作用が生じる可能性があるため。
// bad
function f1(obj) {
obj.key = 1;
}
// good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}
7.13 パラメータを再割り当てしない。
- ESLintルール:
no-param-reassign
// bad
function f1(a) {
a = 1;
// ...
}
// good
function f3(a) {
const b = a || 1;
// ...
}
7.14 可変引数関数を呼び出す際は、スプレッド構文…を使用することが望ましい。
- ESLintルール:
prefer-spread
- 理由: コンテキストを指定する必要がなく、applyと組み合わせることが難しくないため。
// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);
// good
const x = [1, 2, 3, 4, 5];
console.log(...x);
// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));
// good
new Date(...[2016, 8, 5]);
7.15 複数行のシグネチャや呼び出しを持つ関数は、このガイド内の他の複数行のリストと同様に、各項目が独自の行になるようにインデントする。最後の項目には末尾のカンマをつける。
- ESLintルール:
function-paren-newline
// bad
function foo(bar,
baz,
quux) {
// ...
}
// good
function foo(
bar,
baz,
quux,
) {
// ...
}
8. アロー関数
8.1 無名関数が必要な場合(例えば、インラインのコールバックを渡す場合など)、アロー関数記法を使用する。
- ESLintルール: prefer-arrow-callback, arrow-spacing
- 理由: 通常は望ましいthisのコンテキストで実行される関数バージョンを作成し、より簡潔な構文となる。
// bad
[1, 2, 3].map(function (x) {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
8.2 関数本体が副作用のない式を返す単一の文から構成されている場合、波括弧を省略して暗黙的なリターンを使用する。それ以外の場合は、波括弧を維持し、return文を使用する。
- ESLintルール: arrow-parens, arrow-body-style
// bad
[1, 2, 3].map((number) => {
const nextNumber = number + 1;
`A string containing the ${nextNumber}.`;
});
// good
[1, 2, 3].map((number) => `A string containing the ${number + 1}.`);
// good
[1, 2, 3].map((number) => {
const nextNumber = number + 1;
return `A string containing the ${nextNumber}.`;
});
8.3 式が複数行にわたる場合、より良い可読性のために括弧で囲む。
// bad
['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
);
// good
['get', 'post', 'put'].map((httpMethod) => (
Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
));
理由: 関数が始まり、終わる場所が明確にわかる。
8.4 引数周りには常に括弧を含めて、明確性と一貫性を保つ。
- ESLintルール: arrow-parens
// bad
[1, 2, 3].map(x => x * x);
// good
[1, 2, 3].map((x) => x * x);
理由: 引数を追加または削除する際の差分の変動を最小限に抑える。
8.5 アロー関数の構文(=>)と比較演算子(<=, >=)を混同しない。
- ESLintルール: no-confusing-arrow
// bad
const itemHeight = (item) => item.height <= 256 ? item.largeSize : item.smallSize;
// bad
const itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize;
// good
const itemHeight = (item) => (item.height <= 256 ? item.largeSize : item.smallSize);
// good
const itemHeight = (item) => {
const { height, largeSize, smallSize } = item;
return height <= 256 ? largeSize : smallSize;
};
8.6 暗黙的なリターンを持つアロー関数本体の位置を強制する。
- ESLintルール: implicit-arrow-linebreak
// bad
(foo) =>
bar;
(foo) =>
(bar);
// good
(foo) => bar;
(foo) => (bar);
(foo) => (
bar
)
9. クラス(Classes)
9.1 常にclass
を使用し、プロトタイプを直接操作するのを避ける。
- 理由:
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
を使用する。
- 理由:
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 クラスには指定されていない場合、デフォルトのコンストラクタがある。空のコンストラクタ関数または親クラスに委譲するだけのものは不要。
- 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; }
}
9.7 クラスのメソッドは、外部のライブラリやフレームワークが特定の非静的メソッドの使用を要求している場合を除き、this
を使用するか静的メソッドにするべきである。
- ESLintルール: class-methods-use-this
// bad
class Foo {
bar() {
console.log('bar');
}
}
// good - thisを使用する
class Foo {
bar() {
console.log(this.bar);
}
}
// good - constructorが空
class Foo {
constructor() {
// ...
}
}
// good - thisに依存しないためstatic(静的)メソッドで定義する
class Foo {
static bar() {
console.log('bar');
}
}
このルールは、メソッドがクラスのインスタンスに依存する動作をする場合には、this
キーワードを用いてその依存性を明示するように助言している。もしメソッドがインスタンスに依存しないのであれば、そのメソッドは静的メソッドにすべきである。このようにすることで、コードの読み手に対してメソッドの動作がどのようになっているのかをより明確に伝えることができる。
10. モジュール(Modules)
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 ワイルドカードインポートは使用しない。
理由:
- 名前衝突のリスク: ワイルドカードインポートを使用すると、異なるモジュールから同じ名前の変数や関数をインポートする可能性がある。それにより、名前の衝突を引き起こす可能性がある。
- 可読性:
*
を使ってインポートすると、どの関数や変数がそのファイルで使われているのかが明確ではない。これはコードの可読性を下げ、メンテナンスを困難にする。 - 最適化: バンドラー(例えば、WebpackやRollup)が不必要なコードを除去(Tree Shaking)する際に、ワイルドカードインポートは最適化を困難にする可能性がある。
- 明示性: 個別にインポートすることで、そのファイルで何が必要なのかが明示的になる。これは、他の開発者がコードを読むときや、将来的にコードをリファクタリングする際に有用であるため。
// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';
// good
import AirbnbStyleGuide from './AirbnbStyleGuide';
10.3 インポートから直接エクスポートしない。
- 理由:一行で書けるとはいえ、インポートとエクスポートに明確な方法があると、全体として一貫性がある。
// bad
export { es6 as default } from './AirbnbStyleGuide';
// good
import { es6 } from './AirbnbStyleGuide';
export default es6;
10.4 一つのパスからは一箇所でのみインポートする。
- ESLintルール: no-duplicate-imports
- 理由:同じパスから複数行でインポートすると、コードのメンテナンスが難しくなる。
// bad
import foo from 'foo';
import { named1, named2 } from 'foo';
// good
import foo, { named1, named2 } from 'foo';
// good
import foo, {
named1,
named2,
} from 'foo';
10.5 可変のバインディングをエクスポートしない。
- ESLintルール: import/no-mutable-exports
- 理由:特殊なケースで必要かもしれないが、一般的には定数参照のみをエクスポートすべき。
// bad
let foo = 3;
export { foo };
// good
const foo = 3;
export { foo };
10.6 単一のエクスポートを持つモジュールでは、名前付きエクスポートよりもデフォルトエクスポートを優先する。
- ESLintルール: import/prefer-default-export
- 理由:一つのものだけをエクスポートするファイルを増やすことは、可読性とメンテナビリティに良いため。
// bad
export function foo() {}
// good
export default function foo() {}
Named Export(名前付きエクスポート)とは?
関数や変数をその名前でエクスポートする。インポートする際には、同じ名前を使用してインポートする必要がある(または as
キーワードで別名をつける)。
export function foo() {}
import { foo } from './module';
Default Export(デフォルトエクスポート)とは?
デフォルトエクスポートでは、関数や変数を「デフォルト」でエクスポートする。インポートする際には、任意の名前を使用してインポートすることができる。
export default function foo() {}
import anyNameYouLike from './module';
10.7 すべてのインポートは、非インポート文よりも上に置く
- ESLintルール: import/first
- 理由:インポートは巻き上げられるため、すべてを上部に置くことで予期せぬ挙動を防ぐことができる。
// bad
import foo from 'foo';
foo.init();
import bar from 'bar';
// good
import foo from 'foo';
import bar from 'bar';
foo.init();
10.8 複数行のインポートは、複数行の配列やオブジェクトリテラルと同じようにインデントする。
- ESLintルール: object-curly-newline
- 理由:中括弧は、このスタイルガイド内の他のすべての中括弧ブロックと同じインデントルールに従う。また、末尾のカンマも同様。
// bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';
// good
import {
longNameA,
longNameB,
longNameC,
longNameD,
longNameE,
} from 'path';
10.9 モジュールのインポート文でWebpackローダーの構文を禁止する。
- ESLintルール: import/no-webpack-loader-syntax
- 理由:インポートで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';
10.10 JavaScriptのファイル拡張子を含めない。
- ESLintルール: import/extensions
理由:
- リファクタリングの容易性: ファイルの拡張子を変更する場合(例えば、
.js
から.ts
への変更など)、拡張子を指定している全てのimport
ステートメントも修正する必要が出てくる。拡張子を省略していると、このような追加の修正作業は不要になるため。 - 抽象度の維持: 拡張子を明示すると、そのファイルが何であるか(例: ESモジュール、CommonJSモジュール、TypeScriptファイル等)についての実装詳細が露呈する。拡張子を省くことで、モジュール間の依存関係をより抽象的に保つことができる。
- モジュール解決: 一部のモジュールバンドラーやローダー(例えばWebpackなど)では、拡張子を省略した場合に複数の可能な拡張子(
.js
,.json
,.jsx
,.ts
など)を試してモジュールを解決する。このような挙動をうまく活用するためにも、拡張子は省略することが一般的です。
// bad
import foo from './foo.js';
import bar from './bar.jsx';
import baz from './baz/index.jsx';
// good
import foo from './foo';
import bar from './bar';
import baz from './baz';
11. イテレータとジェネレータ(Iterators and Generators)
11.1 イテレータを使用せず、JavaScriptの高階関数をfor-inやfor-ofループの代わりに用いる
map()
、every()
、filter()
、find()
、findIndex()
、reduce()
、some()
などを使用して配列を反復処理し、Object.keys()
、Object.values()
、Object.entries()
を使用して配列を生成し、オブジェクトを反復処理できるようにする。
- ESLintルール: no-iterator no-restricted-syntax
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);
12. プロパティ
12.1 プロパティにアクセスする際はドット記法を使用する
- ESLintルール: dot-notation
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');
12.3 べき乗の計算にはべき乗演算子**を使用する
- ESLintルール: prefer-exponentiation-operator
// bad
const binary = Math.pow(2, 10);
// good
const binary = 2 ** 10;
13. 変数(Variables)
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
- 理由: この方法で新しい変数宣言が追加しやすく、;と,の取り替えや、約物のみの差分を気にする必要がなくなる。デバッガで各宣言を一つずつステップ実行できる。
// 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 必要な場所で変数を代入するが、合理的な場所に配置する
- 理由: letとconstはブロックスコープであり、関数スコープではない。
// bad - unnecessary function call
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 変数代入をチェーンしない
- ESLintルール: no-multi-assign
- 理由: 変数代入のチェーンは暗黙のグローバル変数を作成する。
// 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
13.6 単項のインクリメントとデクリメント(++, –)を使用しない
- ESLintルール: no-plusplus
- 理由: ESLintのドキュメントによれば、単項のインクリメントとデクリメント文は自動セミコロン挿入の対象であり、アプリケーション内で値を増減させる際に無言のエラーを引き起こす可能性がある。また、num += 1のような文で値を変更する方が、num++やnum–を使用するよりも表現が豊かである。単項のインクリメントとデクリメント文を禁止することで、意図せずに値を先に増減させることも防ぐことができ、プログラム内で予期せぬ挙動を引き起こす可能性が減る。
// bad
const array = [1, 2, 3];
let num = 1;
num++;
--num;
// good
const array = [1, 2, 3];
let num = 1;
num += 1;
num -= 1;
13.7 代入において、=の前後に改行を入れない。もし代入がmax-lenを破っている場合は、値を括弧で囲む。
- ESLintルール: operator-linebreak
- 理由: =を囲む改行は、代入の値が何であるかをわかりにくくする。
// bad
const foo =
superLongLongLongLongLongLongLongLongFunctionName();
// bad
const foo
= 'superLongLongLongLongLongLongLongLongString';
// good
const foo = (
superLongLongLongLongLongLongLongLongFunctionName()
);
// good
const foo = 'superLongLongLongLongLongLongLongLongString';
13.8 未使用の変数は許容しない。
- ESLintルール: no-unused-vars
- 理由: コード内で宣言されているがどこにも使用されていない変数は、おそらく不完全なリファクタリングによるエラーである。そのような変数はコード内でスペースを取り、読者に混乱を引き起こす可能性がある。
// bad どこでも使用されていない
const some_unused_var = 42;
// Write-only variables are not considered as used.
let y = 10;
y = 5;
// A read for a modification of itself is not considered as used.
let z = 0;
z = z + 1;
// 引数yが使用されていない
function getX(x, y) {
return x;
}
// good
function getXPlusY(x, y) {
return x + y;
}
14. ホイスティング(Hoisting)
ホイスティング(Hoisting)はJavaScriptにおいて、変数や関数の宣言をコードの最上部に「持ち上げる」ような動作を指す。
ホイスティングの例
varによる変数宣言
javascriptCopy codeconsole.log(a); // 出力: undefined
var a = 5;
console.log(a); // 出力: 5
上記のコードは、実際には次のように解釈される。
var a;
console.log(a); // 出力: undefined
a = 5;
console.log(a); // 出力: 5
letとconstによる宣言
let
とconst
は、var
とは異なりホイスティングされた場合にundefined
が割り当てられない。代わりに、宣言前にアクセスしようとするとReferenceErrorが発生する。
console.log(b); // ReferenceError
let b = 5;
14.1 var宣言は、最も近い外側の関数スコープの先頭に巻き上げられるが、その代入はされない。constとlet宣言は、新しい概念であるTemporal Dead Zones(TDZ)という特性を持っている。typeofがもはや安全でない理由を理解することが重要である。
Temporal Dead Zoneとは?
Temporal Dead Zone(TDZ、一時的死領域)は、JavaScriptのlet
およびconst
キーワードによる変数宣言に関連する概念。var
による変数宣言がホイスティングされて初期値がundefined
とされるのとは対照的に、let
とconst
で宣言された変数は、宣言される行に到達するまで参照できない。この「到達するまでの期間」をTemporal Dead Zoneと呼ぶ。
function example() {
console.log(notDefined); // => throws a ReferenceError
}
function example() {
console.log(declaredButNotAssigned); // => undefined
var declaredButNotAssigned = true;
}
function example() {
let declaredButNotAssigned;
console.log(declaredButNotAssigned); // => undefined
declaredButNotAssigned = true;
}
// using const and let
function example() {
console.log(declaredButNotAssigned); // => throws a ReferenceError
console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
const declaredButNotAssigned = true;
}
14.2 匿名関数式は、その変数名を巻き上げるが、関数の代入は巻き上げない。
function example() {
console.log(anonymous); // => undefined
anonymous(); // => TypeError anonymous is not a function
var anonymous = function () {
console.log('anonymous function expression');
};
}
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('Flying');
};
}
// the same is true when the function name
// is the same as the variable name.
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(); // => Flying
function superPower() {
console.log('Flying');
}
}
14.5 変数、クラス、関数は、使用する前に定義されていなければならない。
- ESLintルール: no-use-before-define
- 理由: 変数、クラス、または関数が使用された後に宣言されると、可読性が損なわれる可能性がある。読者が何かが参照されるものであるかを初めて知る場合、そのソース(別のモジュールからインポートされたもの、またはファイル内で定義されたもの)に最初に遭遇する方がはるかに明確である。
// bad
console.log(a); // this will be undefined, since while the declaration is hoisted, the initialization is not
var a = 10;
// Function は定義前にお使用されている。
fun();
function fun() {}
// Class A は定義前に使用されている
new A(); // ReferenceError: Cannot access 'A' before initialization
class A {
}
// `let` と `const` で定義される前に参照しようとしている
console.log(a); // ReferenceError: Cannot access 'a' before initialization
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let a = 10;
const b = 5;
// good
var a = 10;
console.log(a); // 10
function fun() {}
fun();
class A {
}
new A();
let a = 10;
const b = 5;
console.log(a); // 10
console.log(b); // 5
15. 比較演算子 & 等価性(Comparison Operators & Equality)
15.1 ==
と !=
の代わりに ===
と !==
を使用する
- ESLintルール: eqeqeq
15.2 if
文などの条件文は、ToBoolean抽象メソッドを用いた型強制で式を評価し、常に簡単なルールに従う
- オブジェクトは
true
と評価される undefined
はfalse
と評価されるnull
はfalse
と評価される- ブーリアン(真偽値)はそのブーリアンの値そのものと評価される(
true
またはfalse
) - 数値は
+0
、-0
、NaN
の場合はfalse
、それ以外の場合はtrue
と評価される - 文字列は空文字列
''
の場合はfalse
、それ以外の場合はtrue
と評価される
if ([0] && []) {
// true
// 配列(空であっても)はオブジェクトであり、オブジェクトは true と評価される
}
この例では、[0] && []
の部分が true
と評価される。なぜなら、配列(空であっても)はオブジェクトであり、上述のルールによればオブジェクトは true
と評価されるからだ。このように、JavaScriptにおける条件文の評価は一見直感的でない場合もあるため、このようなルールを理解しておくことは重要である。
15.3 ブール値にはショートカットを使用し、文字列と数値には明示的な比較を使用する
// bad
if (isValid === true) {
// ...
}
// good
if (isValid) {
// ...
}
// bad
if (name) {
// ...
}
// good
if (name !== '') {
// ...
}
// bad
if (collection.length) {
// ...
}
// good
if (collection.length > 0) {
// ...
}
15.5 case
と default
節にレキシカル宣言(例: let, const, function, class)を含む場合は、ブロックを作成するために中括弧を使用する
- ESLintルール: no-case-declarations
- 理由: レキシカル宣言はswitchブロック全体で可視であるが、そのケースが到達したときにのみ初期化される。これが複数のcase節で同じものを定義しようとすると問題を引き起こす。
// 良くない例
switch (foo) {
case 1:
let x = 1;
break;
case 2:
const y = 2;
break;
case 3:
function f() {
// ...
}
break;
default:
class C {}
}
// 良い例
switch (foo) {
case 1: {
let x = 1;
break;
}
case 2: {
const y = 2;
break;
}
case 3: {
function f() {
// ...
}
break;
}
case 4:
bar();
break;
default: {
class C {}
}
}
良い例において、各case
句とdefault
句はそれ自体が独立したブロックである。これにより、各case
句内でのレキシカル宣言が他のcase
句に影響を与えることがなく、スコープがきちんと管理される。このようにして、コードの読みやすさとメンテナンス性が向上する。
15.6 三項演算子はネストしないし、基本的には一行での式であるべきである
- ESLintルール: no-nested-ternary
// bad
const foo = maybe1 > maybe2
? "bar"
: value1 > value2 ? "baz" : null;
// split into 2 separated ternary expressions
const maybeNull = value1 > value2 ? 'baz' : null;
// better
const foo = maybe1 > maybe2
? 'bar'
: maybeNull;
// best
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
15.7 不必要な三項演算子文は避ける
- ESLintルール: no-unneeded-ternary
// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;
const quux = a != null ? a : b;
// good
const foo = a || b;
const bar = !!c;
const baz = !c;
const quux = a ?? b;
15.8 演算子を混在させる場合は、括弧で囲む。
唯一の例外は、標準的な算術演算子(+, -, **)であり、その優先順位が広く理解されている。
/ と * は括弧で囲むことを推奨する。それらが混ざると優先順位が曖昧になる可能性があるため。
- ESLintルール: no-mixed-operators
- 理由: これにより可読性が向上し、開発者の意図が明確になる。
// bad
const foo = a && b < 0 || c > 0 || d + 1 === 0;
// good
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
// bad
const bar = a ** b - 5 % d;
// good
const bar = a ** b - (5 % d);
// bad
if (a || b && c) {
return d;
}
// good
if (a || (b && c)) {
return d;
}
15.9 Null合体演算子(??)は、左辺のオペランドがnullまたはundefinedの場合に、右辺のオペランドを返す論理演算子である。それ以外の場合は、左辺のオペランドを返す。
- 理由: これによりnull/undefinedと他のfalsyな値とを正確に区別し、コードの明瞭性と予測可能性が向上する。
// bad: 0はnullまたはundefinedに該当しない
const value = 0 ?? 'default';
// returns 0, not 'default'
// bad: 空文字はnullまたはundefinedに該当しない
const value = '' ?? 'default';
// returns '', not 'default'
// good
const value = null ?? 'default';
// returns 'default'
// good
const user = {
name: 'John',
age: null
};
const age = user.age ?? 18;
// returns 18
16. ブロック(Blocks)
16.1 複数行のブロックには中括弧を使用する
- ESLintルール: nonblock-statement-body-position
// bad
if (test)
return false;
// good
if (test) return false;
// good
if (test) {
return false;
}
// bad
function foo() { return false; }
// good
function bar() {
return false;
}
16.2 if
と else
の複数行のブロックを使用する場合は、else
をif
ブロックの閉じ中括弧と同じ行に置く
- ESLintルール: brace-style
// bad
if (test) {
thing1();
thing2();
}
else {
thing3();
}
// good
if (test) {
thing1();
thing2();
} else {
thing3();
}
16.3 if
ブロックが常に return
文を実行する場合、その後の else
ブロックは不必要である。return
が含まれる if
ブロックに続く else if
ブロックの return
は、複数の if
ブロックに分割できる
- ESLintルール: no-else-return
// bad
function foo() {
if (x) {
return x;
} else {
return y;
}
}
// good
function foo() {
if (x) {
return x;
}
return y;
}
17. 制御文(Control Statements)
17.1 制御文(if、while など)が長すぎるか、最大行長を超える場合、各(グループ化された)条件を新しい行に置く。論理演算子は行の先頭で始める
- 理由: 行の先頭で演算子を要求することで、演算子が整列され、メソッドチェーンに類似したパターンが維持される。これにより、複雑なロジックを視覚的に追いやすくし、可読性が向上する。
// bad
if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
thing1();
}
// good
if (
(foo === 123 || bar === 'abc')
&& doesItLookGoodWhenItBecomesThatLong()
&& isThisReallyHappening()
) {
thing1();
}
// bad
if (foo === 123 &&
bar === 'abc') {
thing1();
}
// good
if (
foo === 123
&& bar === 'abc'
) {
thing1();
}
17.2 制御文(if
、while
など)の代わりに選択演算子(ここでは &&
が使われている)を使用しない
- 可読性:
if
文を使用した方が、コードの意図が明確になり、より直感的に理解しやすくなる。特に複数の人が参加するプロジェクトでは、一見スマートに見える短縮記法は、他の開発者がコードを読む際に混乱を招く可能性がある。 - デバッグ:
if
文を使った場合、デバッガで簡単にブレークポイントを設定できる。一方で、選択演算子を使った一行のコードではブレークポイントの設定が難しい。 - 拡張性: 将来的に制御フローが複雑になった場合(例えば、新しい条件や追加の処理が必要になった場合)、
if
文を使用していればコードを拡張しやすい。
// bad
!isRunning && startRunning();
// good
if (!isRunning) {
startRunning();
}
18. コメント
18.1 複数行のコメントには /** ... */
を使用する
// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
// ...
return element;
}
// good
/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
// ...
return element;
}
18.2 単一行のコメントには //
を使用する。単一行のコメントは、コメントの対象の上の新しい行に配置する。コメントの前には空行を置く、ただしブロックの最初の行の場合は除く
// bad
const active = true; // is current tab
// good
// is current tab
const active = true;
// bad
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this.type || 'no type';
return type;
}
// good
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this.type || 'no type';
return type;
}
// also good
function getType() {
// set the default type to 'no type'
const type = this.type || 'no type';
return type;
}
18.3 コメントはすべてスペースで始める。これにより読みやすくなる
- ESLintルール: spaced-comment
// bad
//is current tab
const active = true;
// good
// is current tab
const active = true;
// bad
/**
*make() returns a new element
*based on the passed-in tag name
*/
function make(tag) {
// ...
return element;
}
// good
/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
// ...
return element;
}
18.4 コメントに FIXME や TODO をプレフィックスとしてつけることで、他の開発者が問題点をすぐに理解したり、問題に対する解決策を提案する場合に有用である。
これらは、行動可能であるという点で通常のコメントとは異なる。行動とは、FIXME: — これを解明する必要がある、または TODO: — 実装する必要がある、である。
18.5 問題を注釈するには // FIXME:
を使用する
class Calculator extends Abacus {
constructor() {
super();
// FIXME: shouldn’t use a global here
total = 0;
}
}
18.6 問題の解決策を注釈するには // TODO:
を使用する
class Calculator extends Abacus {
constructor() {
super();
// TODO: total should be configurable by an options param
this.total = 0;
}
}
19. 空白
19.1 ソフトタブ(スペース文字)を2スペースで設定する。
- ESLintルール: indent
// bad
function foo() {
∙∙∙∙let name;
}
// bad
function bar() {
∙let name;
}
// good
function baz() {
∙∙let name;
}
19.2 先頭の中括弧の前に1つのスペースを配置する。
- ESLintルール: space-before-blocks
// bad
function test(){
console.log('test');
}
// good
function test() {
console.log('test');
}
// bad
dog.set('attr',{
age: '1 year',
breed: 'Bernese Mountain Dog',
});
// good
dog.set('attr', {
age: '1 year',
breed: 'Bernese Mountain Dog',
});
19.3 制御文(if、while など)の開始括弧の前に1つのスペースを置く。関数呼び出しと宣言で、引数リストと関数名の間にスペースは置かない。
- ESLintルール: keyword-spacing
// bad
if(isJedi) {
fight ();
}
// good
if (isJedi) {
fight();
}
// bad
function fight () {
console.log ('Swooosh!');
}
// good
function fight() {
console.log('Swooosh!');
}
19.4 演算子はスペースで区切る。
- ESLintルール: space-infix-ops
// bad
const x=y+5;
// good
const x = y + 5;
19.5 ファイルは1つの改行文字で終了する。
- ESLintルール: eol-last
// bad
import { es6 } from './AirbnbStyleGuide';
// ...
export default es6;↵
↵
// good
import { es6 } from './AirbnbStyleGuide';
// ...
export default es6;↵
19.6 長いメソッドチェーン(2つ以上のメソッドチェーン)を作成するときはインデントを使用する。先頭にドットを使用し、その行がメソッド呼び出しであることを強調する。
- ESLintルール: newline-per-chained-call, no-whitespace-before-property
// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();
// bad
$('#items').
find('.selected').
highlight().
end().
find('.open').
updateCount();
// good
$('#items')
.find('.selected')
.highlight()
.end()
.find('.open')
.updateCount();
19.7 ブロックの後と次の文の前には空白行を1行残す。
// bad
if (foo) {
return bar;
}
return baz;
// good
if (foo) {
return bar;
}
return baz;
19.8 ブロックを空白行で詰めない。
- ESLintルール: padded-blocks
// bad
function bar() {
console.log(foo);
}
// good
function bar() {
console.log(foo);
}
19.9 複数の空白行を使用してコードを詰めない。
- ESLintルール: no-multiple-empty-lines
// bad
class Person {
constructor(fullName, email, birthday) {
this.fullName = fullName;
this.email = email;
this.setAge(birthday);
}
setAge(birthday) {
const today = new Date();
const age = this.getAge(today, birthday);
this.age = age;
}
getAge(today, birthday) {
// ..
}
}
// good
class Person {
constructor(fullName, email, birthday) {
this.fullName = fullName;
this.email = email;
this.setAge(birthday);
}
setAge(birthday) {
const today = new Date();
const age = getAge(today, birthday);
this.age = age;
}
getAge(today, birthday) {
// ..
}
}
19.10 括弧内にスペースを追加しない。
- ESLintルール: space-in-parens
// bad
function bar( foo ) {
return foo;
}
// good
function bar(foo) {
return foo;
}
// bad
if ( foo ) {
console.log(foo);
}
// good
if (foo) {
console.log(foo);
}
19.11 角括弧内にスペースを追加しない。
- ESLintルール: array-bracket-spacing
// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);
// good
const foo = [1, 2, 3];
console.log(foo[0]);
19.12 中括弧内にスペースを追加する。
- ESLintルール: object-curly-spacing
// bad
const foo = {clark: 'kent'};
// good
const foo = { clark: 'kent' };
19.13 100文字(空白を含む)を超える行を持たない。
- ESLintルール: max-len
// bad
const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;
// good
const foo = jsonData
&& jsonData.foo
&& jsonData.foo.bar
&& jsonData.foo.bar.baz
&& jsonData.foo.bar.baz.quux
&& jsonData.foo.bar.baz.quux.xyzzy;
19.14 開くブロックトークンと同じ行の次のトークンの間に一貫したスペーシングが必要である。
- ESLintルール: block-spacing
// bad
function foo() {return true;}
if (foo) { bar = 0;}
// good
function foo() { return true; }
if (foo) { bar = 0; }
19.16 計算されたプロパティの角括弧内のスペーシングを強制する。
- ESLintルール: computed-property-spacing
// bad
obj[foo ]
obj[ 'foo']
const x = {[ b ]: a}
obj[foo[ bar ]]
// good
obj[foo]
obj['foo']
const x = { [b]: a }
obj[foo[bar]]
19.17 関数とその呼び出しの間にスペースを避ける。
- ESLintルール: func-call-spacing
// bad
func ();
func
();
// good
func();
19.18 オブジェクトリテラルのプロパティでキーと値の間にスペーシングを強制する。
- ESLintルール: key-spacing
// bad
const obj = { foo : 42 };
const obj2 = { foo:42 };
// good
const obj = { foo: 42 };
19.19 行の最後に余分なスペースを残さない。
- ESLintルール: no-trailing-spaces
19.20 複数の空行を避け、ファイルの最後には1つの改行のみを許可する。また、ファイルの先頭で改行を避ける。
- ESLintルール: no-multiple-empty-lines
// bad - multiple empty lines
const x = 1;
const y = 2;
// bad - 2+ newlines at end of file
const x = 1;
const y = 2;
// bad - 1+ newline(s) at beginning of file
const x = 1;
const y = 2;
// good
const x = 1;
const y = 2;
20. カンマ
20.1 先行するカンマ: なし。
- ESLintルール: comma-style
// bad
const story = [
once
, upon
, aTime
];
// good
const story = [
once,
upon,
aTime,
];
20.2 追加の末尾のカンマ: あり。
- ESLintルール: comma-dangle
- 理由: これによりgitの差分が綺麗になる。また、Babelのようなトランスパイラは、トランスパイルされたコードで追加の末尾のカンマを削除するため、古いブラウザでの末尾のカンマの問題を心配する必要はない。
// bad
const hero = {
firstName: 'Dana',
lastName: 'Scully'
};
// good
const hero = {
firstName: 'Dana',
lastName: 'Scully',
};
21. セミコロン(Semicolons)
21.1 Yup
- ESLintルール: semi
- 理由: JavaScriptがセミコロンなしで改行を検出した場合、自動セミコロン挿入(ASI)と呼ばれる一連のルールを使用して、その改行を文の終わりと見なすかどうかを判断する。ASIにはいくつか変わった挙動があり、JavaScriptが改行を誤解した場合、コードは壊れる。これらのルールは、新しい機能がJavaScriptの一部になるにつれて、より複雑になる。文を明示的に終了させ、セミコロンが不足している場合にリンターで確認することが、問題を防ぐ手助けとなる。
// bad - raises exception
const luke = {}
const leia = {}
[luke, leia].forEach((jedi) => jedi.father = 'vader')
// good
const luke = {};
const leia = {};
[luke, leia].forEach((jedi) => {
jedi.father = 'vader';
});
22. 型変換 & 強制(Type Casting & Coercion)
22.1 ステートメントの始めに型強制を実行する。
22.2 文字列
- ESLintルール: no-new-wrappers
// => this.reviewScore = 9;
// bad
// これによって生成されるtotalScoreは、プリミティブ文字列ではなく、文字列オブジェクトになる
const totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string"
// bad
// 数値を空の文字列に連結することで文字列に変換している。
// これによって、this.reviewScore.valueOf()が呼び出される。
const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf()
// bad
// this.reviewScoreがnullまたはundefinedの場合、toStringメソッドはエラーを投げる。
// この方法は文字列が返ることを保証しない。
const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string
// good
const totalScore = String(this.reviewScore);
goodの方法で変換されたtotalScore
はプリミティブ文字列であり、this.reviewScore
がnullやundefinedの場合でも適切に文字列に変換されるため、この方法が推奨される。
22.3 数値: 数値型への型変換にはNumber
を使用し、文字列をパースする際は、必ず基数(radix)を指定してparseInt
を使用する。
- ESLintルール: radix, no-new-wrappers
- 理由:
parseInt
関数は、指定された基数に基づいて文字列引数の内容を解釈し、整数値を生成する。文字列の先頭の空白は無視される。基数が未定義または0の場合、数値が0xまたは0Xで始まる場合を除き、10とみなされる。ECMAScript 3では、8進解釈は許容されていたが非推奨だった。多くの実装が2013年時点でこの挙動を採用していない。そして、古いブラウザをサポートしなければならないので、常に基数を指定する。
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);
22.4 parseInt
がボトルネックになり、パフォーマンスの理由でビットシフトを使用する必要がある場合は、コメントでその理由とやっていることを説明する。
// good
/**
* parseInt was the reason my code was slow.
* Bitshifting the String to coerce it to a
* Number made it a lot faster.
*/
const val = inputValue >> 0;
22.5 注意
ビットシフト演算子を使用する際は注意すること。数値は64ビット値として表されるが、ビットシフト演算は常に32ビット整数を返す(ソース)。ビットシフトは、32ビットを超える整数値に対して予期せぬ挙動を引き起こす可能性がある。議論。最大の符号付き32ビットIntは2,147,483,647である:
2147483647 >> 0; // => 2147483647
2147483648 >> 0; // => -2147483648
2147483649 >> 0; // => -2147483647
22.6 Booleans
- ESLintルール: no-new-wrappers
- 理由:
const age = 0;
// bad
// new Booleanを使用すると、結果としてBooleanオブジェクトが生成される
const hasAge = new Boolean(age);
// good
// Boolean(age)を使用すると、期待通りのプリミティブ型のtrueまたはfalseが得られる。
const hasAge = Boolean(age);
// best
const hasAge = !!age;
bestの方法では、age
を論理値に変換するために、論理NOT演算子!
を二回適用する。1回目の!
がage
を論理値に変換し、その結果を反転させる。2回目の!
が再びその結果を反転させ、元の論理値の反対の値を得る。
23. 命名規則(Naming Conventions)
23.1 一文字の名前は避ける。命名には説明的であること。
- ESLintルール: id-length
// bad
function q() {
// ...
}
// good
function query() {
// ...
}
23.2 オブジェクト、関数、インスタンスの命名にはcamelCaseを使用する。
- ESLintルール: camelcase
// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}
// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
23.3 コンストラクタまたはクラスを命名する際にのみPascalCaseを使用する。
- ESLintルール: new-cap
// bad
function user(options) {
this.name = options.name;
}
const bad = new user({
name: 'nope',
});
// good
class User {
constructor(options) {
this.name = options.name;
}
}
const good = new User({
name: 'yup',
});
23.4 アンダースコアを先頭または末尾に使用しない。
- ESLintルール: no-underscore-dangle
- 理由: JavaScriptには、プロパティやメソッドに対してプライバシーの概念がない。アンダースコアが「プライベートを意味する一般的な慣習であるが、これらのプロパティは完全に公開されており、そのため、あなたの公開API契約の一部となる。この慣習は、開発者が変更が破壊的でないと誤って考えたり、テストが不要であると考えたりすることを引き起こす可能性がある。要するに、何かを「プライベート」としたい場合、それは観察できない形で存在しなければならない。
// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
this._firstName = 'Panda';
// good
this.firstName = 'Panda';
23.5 this
への参照を保存しない。アロー関数またはFunction#bindを使用する。
// bad
function foo() {
const self = this;
return function () {
console.log(self);
};
}
// good
function foo() {
return () => {
console.log(this);
};
}
23.6 ベースのファイル名は、そのデフォルトのエクスポートの名前と完全に一致すべきである。
// bad
import CheckBox from './check_box';
// good
import CheckBox from './CheckBox';
23.7 関数をexport-defaultする際にはcamelCaseを使用する。ファイル名は関数の名前と同一であるべきである。
function makeStyleGuide() {
// ...
}
export default makeStyleGuide;
23.8 コンストラクタ / クラス / シングルトン / 関数ライブラリ / 裸のオブジェクトをエクスポートする際にはPascalCaseを使用する。
const AirbnbStyleGuide = {
es6: {
},
};
export default AirbnbStyleGuide;
23.9 略語や頭字語は、常にすべて大文字、またはすべて小文字であるべき。
// bad
import SmsContainer from './containers/SmsContainer';
// bad
const HttpRequests = [
// ...
];
// good
import SMSContainer from './containers/SMSContainer';
// good
const HTTPRequests = [
// ...
];
23.10 定数を大文字で記述することができるが、以下の条件を満たす場合に限る。
- エクスポートされる場合
- constである(再代入ができない)
- プログラマがそれ(およびそのネストしたプロパティ)が変更されないと信頼できる場合
全てのconst変数について大文字にするのは不必要であるため、ファイル内の定数に対して大文字を使用するべきではない。ただし、エクスポートされる定数には使用すべきである。
// bad
const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file';
// bad
export const THING_TO_BE_CHANGED = 'should obviously not be uppercased';
// bad
export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables';
// ---
// allowed but does not supply semantic value
export const apiKey = 'SOMEKEY';
// better in most cases
export const API_KEY = 'SOMEKEY';
// bad - unnecessarily uppercases key while adding no semantic value
export const MAPPING = {
KEY: 'value'
};
// good
export const MAPPING = {
key: 'value',
};
24. アクセサ(Accessors)
24.1 プロパティのアクセサ関数は必須ではない。
24.1 JavaScriptのgetters/settersは使用しない。
これらは予期せぬ副作用を引き起こし、テスト、保守、理解が困難である。代わりに、アクセサ関数を作成する場合は、getVal()とsetVal(‘hello’)を使用する。
// bad
class Dragon {
get age() {
// ...
}
set age(value) {
// ...
}
}
// good
class Dragon {
getAge() {
// ...
}
setAge(value) {
// ...
}
}
JavaScriptのgetters/settersは、プロパティへのアクセスや代入に際して、カスタムの挙動を定義するためのもの。これには一見多くの利点があるが、一方で以下のような問題点も存在する。
1. 予期せぬ副作用
gettersやsettersは、外部からは普通のプロパティのように見えるが、内部では関数として動作する。これにより、プロパティへのアクセスや代入が予想外の副作用を引き起こす可能性がある。例えば、値の代入が他のプロパティや変数の変更を引き起こす、あるいは、値の取得が計算を伴う場合など。
class Dragon {
set age(value) {
this._age = value;
this.ageInMonths = value * 12; // 他のプロパティの値も変更
}
}
2. テストの困難さ
gettersやsettersが内部で複雑な処理や副作用を持つ場合、単体テストが困難になる。期待する挙動と異なる挙動が起こる可能性がある。
3. 保守の困難さ
gettersやsettersの背後にある複雑なロジックや副作用により、コードの保守が困難になり得る。新しい開発者がコードを理解し、変更する際に、予期せぬ問題が発生する可能性がある。
4. 理解の困難さ
gettersやsettersが内部で何をしているのかは、外部からは一見して理解しづらいため、コードの可読性や理解の困難さが生じる可能性がある。
代わりに、明示的にメソッドを定義することで、これらの問題を緩和できる。上記のgetAge()
やsetAge(value)
のようなメソッド名は、それがメソッドであることを明示し、何らかの処理や計算を行う可能性があることを示す。これにより、コードの可読性が向上し、予期せぬ副作用のリスクが軽減される。
24.3 プロパティ/メソッドがブーリアンの場合、isVal()またはhasVal()を使用する。
// bad
if (!dragon.age()) {
return false;
}
// good
if (!dragon.hasAge()) {
return false;
}
24.4 get()とset()関数を作成するのは構わないが、一貫性を保つ。
class Jedi {
constructor(options = {}) {
const lightsaber = options.lightsaber || 'blue';
this.set('lightsaber', lightsaber);
}
set(key, val) {
this[key] = val;
}
get(key) {
return this[key];
}
}
コメント