JavaScriptは大変難しい言語です。Rubyの難易度を2、Cの難易度を5、C++の難易度を8にすると、JavaScriptの難易度は12ぐらいあると思います。このコーディングガイドはそんなJavaScriptの深みに嵌まらないようにするためのJavaScriptの書き方を規定したものです。初級者1のための物ですので、わかってやっている人に好きにやってください。
このコーディングガイドは絶対に従わなければならないものではありません。私は一切強制はしませんし、初級者が従わなければならないという義務もありません。採用するしないはみなさんの自由です。
禁止編
JavaScriptには安易に使用してはいけない機能があります。下記の機能は、**それぞれの機能を使っても良い、または、使うべきであるという理由を説明できない限り、**使用してはいけません。
==
、!=
==
と!=
を使用してはいけません。代わりに===
や!==
を使用し、必要に応じて型変換を行ってください。
const x = '1';
if (x == 1) {
console.log('xは1です。');
}
const x = '1';
if (Number(x) === 1) {
console.log('xは1です。');
}
例外は、x != null
でnull
またはundefined
で無い事をチェックする(存在チェック)ことです。
const x = '1';
if (x != null) {
console.log('xは存在します。');
}
var
var
を使用してはいけません。代わりにlet
やconst
を使用してください。
var x = 1;
var y = 2;
x = 3;
let x = 1;
const y = 2;
x = 3;
関数宣言、ジェネレーター宣言、非同期関数宣言
関数宣言、ジェネレーター宣言、非同期関数宣言を使用してはいけません。関数を定義する場合は、関数式またはジェネレーター式、非同期関数式、アロー関数、非同期アロー関数をconst
変数に代入してください。
function helloOne(ok) {
// 厳格モードと非厳格モードでifブロック内の動作が異なる
if (ok) {
function hello() {
console.log('こんにちは');
}
} else {
function hello() {
console.log('世界');
}
}
function* one() {
yield 1;
}
hello();
for (const v of one()) {
console.log(v);
}
}
helloOne(true);
const helloOne = (ok) => {
let hello;
if (ok) {
hello = () => {
console.log('こんにちは');
};
} else {
hello = () => {
console.log('世界');
};
}
const one = function*() {
yield 1;
};
hello();
for (const v of one()) {
console.log(v);
}
};
helloOne(true);
何らかの理由でvar
および関数宣言を使用する場合は、巻き上げと変更可能であることを十分理解していなければなりません。下記のコードの実行結果が予測ができる状態でなければなりません。
var hello = function() {
console.log('世界!');
};
function hello() {
console.log('こんにちは');
}
hello();
クラスおよびメソッドは、式にしなければならない理由が無い限り宣言にしてください。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
isAdult() {
if (this.age >= 20) {
return true;
} else {
return false;
}
}
}
関数式、ジェネレーター式、非同期関数式でコード内にthis
が含まれる場合はthis
がレキシカルに決定されない事を理解しておく必要があります。この文章の意味がわからなければ、使用すべきではありません。
const f = function() {
console.log(this);
console.log(this.x);
};
f(); // undefined
const obj = {x: 42, f: f};
obj.f(); // 42
コールバックに関数式 (this
が明確な場合を除く)
コールバックに関数式を使用してはいけません。代わりにアロー関数を使用してください。
class Calculator {
constructor(number) {
this.number = number;
}
allAdd(arr) {
return arr.map(function(n) {
// thisがundefinedになり、エラー
return n + this.number;
});
}
}
const calc = new Calculator(2);
const list = [2, 3, 5, 7, 11];
const addedList = calc.allAdd(list);
console.log(addedList);
class Calculator {
constructor(number) {
this.number = number;
}
allAdd(arr) {
return arr.map(n => n + this.number);
}
}
const calc = new Calculator(2);
const list = [2, 3, 5, 7, 11];
const addedList = calc.allAdd(list);
console.log(addedList);
this
を明確に指定している、または、渡す関数側でthis
が束縛されるオブジェクトが明確かつ使用される、this
を使用していないという場合は、関数式を使用してもかまいません。
class Calculator {
constructor(number) {
this.number = number;
}
allAdd(arr) {
return arr.map(function(n) {
return n + this.number;
}, this); // map()は第二引数でthisを指定可能
}
}
const calc = new Calculator(2);
const list = [2, 3, 5, 7, 11];
const addedList = calc.allAdd(list);
console.log(addedList);
再帰関数を作る場合は、関数式を使用してもかまいません。ただし、this
に対する注意事項は理解した上で、という条件が付きます。
const list = [2, 3, 5, 7, 11]
const collatzCountList = list.map(
function collatzCount(num, idx, arr, count = 0) {
if (num === 1) return count;
const nextNum = num % 2 === 0 ? num / 2 : 3 * num + 1;
return collatzCount(nextNum, idx, arr, count + 1);
});
console.log(collatzCountList);
ジェネレーター式を使用してもかまいません。ただし、同じくthis
に対する注意事項は理解した上で、という条件が付きます。
const printGenerator = (g) => {
for (const v of g()) {
console.log(v);
}
};
printGenerator(function*() {
yield 'こんにちは';
yield '世界!';
})
コールバックにメソッド
コールバックにメソッドをそのまま使用してはいけません。アロー関数で囲ってください。
class Calculator {
constructor(number) {
this.number = number;
}
add(n) {
return n + this.number;
}
allAdd(arr) {
// 呼び出し先の関数でのthisがundefinedになり、エラー
return arr.map(this.add);
}
}
const calc = new Calculator(2);
const list = [2, 3, 5, 7, 11];
const addedList = calc.allAdd(list);
console.log(addedList);
class Calculator {
constructor(number) {
this.number = number;
}
add(n) {
return n + this.number;
}
allAdd(arr) {
return arr.map(n => this.add(n));
}
}
const calc = new Calculator(2);
const list = [2, 3, 5, 7, 11];
const addedList = calc.allAdd(list);
console.log(addedList);
または、bind()
を用いて束縛するか、this
を明示的に指定してください。
class Calculator {
constructor(number) {
this.number = number;
}
add(n) {
return n + this.number;
}
allAdd(arr) {
return arr.map(this.add.bind(this));
}
}
const calc = new Calculator(2);
const list = [2, 3, 5, 7, 11];
const addedList = calc.allAdd(list);
console.log(addedList);
class Calculator {
constructor(number) {
this.number = number;
}
add(n) {
return n + this.number;
}
allAdd(arr) {
return arr.map(this.add, this);
}
}
const calc = new Calculator(2);
const list = [2, 3, 5, 7, 11];
const addedList = calc.allAdd(list);
console.log(addedList);
constructor()
で、メソッドに対してbind()
を用いてあらかじめthis
に束縛することについては良いか悪いかは保留します。意見募集中。2
class Calculator {
constructor(number) {
this.number = number;
// addの呼び出しは常にthisに束縛されるようになる
this.add = this.add.bind(this);
}
add(n) {
return n + this.number;
}
allAdd(arr) {
return arr.map(this.add);
}
}
const calc = new Calculator(2);
const list = [2, 3, 5, 7, 11];
const addedList = calc.allAdd(list);
console.log(addedList);
arguments
arguments
を使用してはいけません。デフォルト値、可変長引数を使用してください。
const f = function(x) {
var y = arguments[1];
if (y == null) {
y = 0;
}
var args = 3 <= arguments.length ? [].slice.call(arguments, 2) : [];
return console.log(x, y, args);
};
f(0, 1, 2, 3);
const f = (x, y = 0, ...args) => {
console.log(x, y, args);
};
f(0, 1, 2, 3);
for...in
for...in
を使用してはいけません。配列の反復などにはfor...of
、map()
、reduce()
等を使用してください。Objectの場合はObject.keys()
を使用してください。
const obj = Object.create({ x: 42 });
obj.a = 2;
obj.b = 3;
obj.c = 5;
for (const key in obj) {
// hasOwnPropertyでチェックしないとxも見てしまう
if (!obj.hasOwnProperty(key)) continue;
const value = obj[key];
console.log(`${key}: ${value}`);
}
const obj = Object.create({ x: 42 });
obj.a = 2;
obj.b = 3;
obj.c = 5;
for (const key of Object.keys(obj)) {
const value = obj[key];
console.log(`${key}: ${value}`);
}
ECMAScript2017以降が使用できる場合は、Object.entries()
やObject.values()
を使用することもできます。
const obj = Object.create({ x: 42 });
obj.a = 2;
obj.b = 3;
obj.c = 5;
for (const [key, value] of Object.entries(obj)) {
console.log(`${key}: ${value}`);
}
配列反復にfor(;;)
配列反復にfor(;;)
を使用してはいけません。for...of
、map
、reduce
等を使用してください。
const arr = [2, 3, 5, 7, 11];
for (let i = 0, len = arr.length ; i < len; i++) {
console.log(arr[i]);
}
const arr = [2, 3, 5, 7, 11];
for (const x of arr) {
console.log(x);
}
forEach
forEach
を使用してはいけません。for...of
を使用してください。また、可能な限り、map
やreduce
等が使用できないかを検討してください。
const arr = [2, 3, 5, 7, 11];
arr.forEach(x => {
console.log(x);
});
let sum = 0;
arr.forEach(x => {
sum += x;
});
console.log(sum);
const arr = [2, 3, 5, 7, 11];
for (const x of arr) {
console.log(x);
}
const sum = arr.reduce((acc, x) => acc + x, 0);
console.log(sum);
プロトタイプ作成に関数定義
プロトタイプを作成するために関数定義を使用してはいけません。クラス定義を使用してください。
const Person = function(name, age) {
this.name = name;
this.age = age;
};
Person.prototype.isAdult = function() {
if (this.age >= 20) {
return true;
} else {
return false;
}
};
const taro = new Person('Taro', 18);
console.log(taro.isAdult());
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
isAdult() {
if (this.age >= 20) {
return true;
} else {
return false;
}
}
}
const taro = new Person('Taro', 18);
console.log(taro.isAdult());
undefined
(保証できないとき)
undefined
がundefined値であることを保証できない限り、undefined
を使用してはいけません。確実にundefined値であることを保証するときはvoid 0
を使用してください。単純にundefined値を返す場合はreturn;
を使用する、undefined値であることを確認する場合はtypeof
を用いて'undefined'
と比較してください。存在チェック(null
またはundefined
ではない)はx != null
で十分です。
const undefined = 0;
const f = () => {
return undefined;
}
const x = 0;
const y = undefined;
const z = f();
console.log(x, x === undefined, x != null);
console.log(y, y === undefined, y != null);
console.log(z, z === undefined, z != null);
const undefined = 0;
const f = () => {
return;
}
const x = 0;
const y = void 0;
const z = f();
console.log(x, typeof x === 'undefined', x != null);
console.log(y, typeof y === 'undefined', y != null);
console.log(z, typeof z === 'undefined', z != null);
Infinity
、NaN
(保証できないとき)
Infinity
やNaN
が無限や非数であることを保証できない限り、Infinity
とNaN
を使用してはいけません。確実に∞値や非数値であることを保証するときは1/0
や0/0
など計算結果を用いてください。
const Infinity = 1;
const NaN = 0;
console.log(Infinity);
console.log(NaN);
const Infinity = 1;
const NaN = 0;
console.log(1/0);
console.log(0/0);
NaN
(比較)
NaN
(非数)のチェックに(たとえ、NaN
が非数値であると保証されていても)NaN
を使用してはいけません。代わりにisNaN()
を使用してください。3
const x = 0/0;
// xはNaNなのにfalseになる
console.log(x === NaN);
const x = 0/0;
console.log(isNaN(x));
delete
delete
を使用してはいけません。Arrayはshift()
、splice()
、pop()
を使用してください。連想配列はMapを使用してください。レコードはプロパティを削除しないでください。
const arr = [2, 3, 5, 7, 11];
delete arr[arr.length - 1];
const dic = {
'林檎': '赤色',
'蜜柑': '黄色',
'桃': '桃色'
};
delete dic['林檎'];
const apple = {
name: '林檎',
color: '赤色',
sugar: 15
};
delete apple.color;
console.log(arr);
console.log(dic);
console.log(apple);
const arr = [2, 3, 5, 7, 11];
arr.pop();
const dic = new Map([
['林檎', '赤色'],
['蜜柑', '黄色'],
['桃', '桃色']
]);
dic.delete('林檎');
const apple = {
name: '林檎',
color: '赤色',
sugar: 15
};
apple.color = null;
console.log(arr);
console.log(dic);
console.log(apple);
厳格モードで削除される機能
厳格モードでは削除される機能(with
文、暗黙のグローバル変数、8進数表記等)を使用してはいけません。なお、クラスやモジュールでは常に厳格モードになります。クラスやモジュールとして作成する場合は、'use strict';
は書かないようにしてください。
with (Math) {
TAU = PI * 02;
}
console.log(global.TAU);
'use strict';
global.TAU = Math.PI * 2;
console.log(golbal.TAU);
暗黙のセミコロン;
文末のセミコロン;
を省略してはいけません。暗黙のセミコロン;
を期待してはいけません。
const x = 1
console.log(x)
const x = 1;
console.log(x);
{
の前での改行
ブロックやObjectリテラルの開始である{
の前で改行してはいけません。決してオールマンスタイルを採用してはいけません。字下げスタイルはJavaスタイルを採用してください。
class Person
{
constructor(name, age)
{
this.name = name;
this.age = age;
}
isAdult()
{
if (this.age >= 20)
{
return true;
}
else
{
return false;
}
}
nameInfo()
{
// 構文エラー
return
{
name: this.name,
length: this.name.length
};
}
}
const taro = new Person('Taro', 18);
console.log(taro.isAdult());
console.log(taro.nameInfo());
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
isAdult() {
if (this.age >= 20) {
return true;
} else {
return false;
}
}
nameInfo() {
return {
name: this.name,
length: this.name.length
};
}
}
const taro = new Person('Taro', 18);
console.log(taro.isAdult());
console.log(taro.nameInfo());
return
のみの行
return
のみの行を作成してはいけません。undefined値を返したい場合はreturn;
としてください。次の行が続くのであれば、()
で括ってください。
const f = (x) => {
const i = 2;
// undefinedが返る
return
x * i;
}
console.log(f(3));
const f = (x) => {
const i = 2;
return (
x * i
);
}
console.log(f(3));
即時関数で囲む
名前空間保護のために即時関数で囲む方法を使用してはいけません。代わりにモジュールベースで作成してください。
(function(globalObj){
function hello() {
console.log('hello');
}
globalObj.hello = hello;
})(this);
export function hello() {
console.log('hello');
}
UTF-8以外のエンコード
エンコードはUTF-8を使用し、それ以外は使用しないでください。
ES5以前のJavaScript
ES6未対応ブラウザ(IE等)のために、ES5以前のJavaScript直接記述してはいけません。ECMAScript 2016以降(Babelでの変換が前提)を用いるか、TypeScript、CoffeeScript、LiveScript、PureScript、Opal等のAltJSで作成してください。4
Underscore
Underscoreを使用してはいけません。ほとんどの場合は不要です。もし、どうしても必要になる場合はLodashを使用してください。
jQueryによるDOM操作
jQueryでDOM操作をしてはいけません。JavaScriptのみでjQueryと同様のDOM操作は可能です。複雑なDOM操作を行いたい場合はReactやAngularJS等を検討してください。
IE対応
IEは捨ててください。考えるだけ無駄です。
推奨編
const
可能な限り変数はconst
を付けて定数にしてください。変更する必要がある場合のみlet
を使用してください。
import
export
CommonJSであってもrequire()
ではなくimport
とexport
の構文を使用してください。BabelやTypeScriptを正しく設定すれば、自動的にCommonJSやAMDの形式に変換してくれます。
アロー関数
関数式を使用しなければならない理由が無ければ、アロー関数を使用してください。一つの式のみでその結果を返す場合は、より簡潔に書くことができます。
Airbnb JavaScript Style Guide
スタイルガイドはAirbnb JavaScript Style Guide(以下Airbnb)を使用してください。この資料のほとんどの項目はこのスタイルガイドと同じになっています。
日本語訳もありますが、内容が古くなっています。Airbnbは、より良いものを模索しているため、常に更新され続けています。最新情報はgithub上のレポジトリを確認してください。
補足
このガイドにはなぜそうするのかの理由は書いてません。理由がどうしても知りたいという方は、@ms2satoさんが書いてくれた素晴らしい補足『「JavaScript初級者のためのコーディングガイド」に補足を試みる』をお読みください5。ただし、補足を読む前に、一度自分で理由を考えてください。他人の言葉ではなく自分の言葉で説明できて初めて意味があります。それができないうちは使用禁止です!
-
真にJavaScriptを学ぼうとする人の事です。使おうとする人のことではありません。 ↩
-
この書き方はReactのマニュアルHandling Eventsで紹介されている方法です。 ↩
-
JavaScriptの数値はIEEE 754で64bit binary(倍精度浮動小数点数)です。この浮動小数点数には数では無い(非数)ことを表すNaNが用意されています。通常、ほとんどの実装において、NaNは何と同じかどうかの比較をしても(NaN自身と比較をしても)必ず偽になります。 ↩
-
ECMAScript 2016とTypeScriptについては、このコーディングガイドの通りでしょう。しかし、他のaltJSはそのままでは採用できない物もあります。それらは、それらの流儀にしたがってください。 ↩