JavaScript初級者のためのコーディングガイド

  • 1105
    いいね
  • 44
    コメント

JavaScriptは大変難しい言語です。Rubyの難易度を2、Cの難易度を5、C++の難易度を8にすると、JavaScriptの難易度は12ぐらいあると思います。このコーディングガイドはそんなJavaScriptの深みに嵌まらないようにするためのJavaScriptの書き方を規定したものです。初級者1のための物ですので、わかってやっている人に好きにやってください。

このコーディングガイドは絶対に従わなければならないものではありません。私は一切強制はしませんし、初級者が従わなければならないという義務もありません。採用するしないはみなさんの自由です。

禁止編

JavaScriptには安易に使用してはいけない機能があります。下記の機能は、それぞれの機能を使っても良い、または、使うべきであるという理由を説明できない限り、使用してはいけません。

:no_entry_sign: ==!=

==!=を使用してはいけません。代わりに===!==を使用し、必要に応じて型変換を行ってください。

悪い例.js
const x = '1';
if (x == 1) {
  console.log('xは1です。');
}
良い例.js
const x = '1';
if (Number(x) === 1) {
  console.log('xは1です。');
}

例外は、x != nullnullまたはundefinedで無い事をチェックする(存在チェック)ことです。

存在チェックは良い.js
const x = '1';
if (x != null) {
  console.log('xは存在します。');
}

:no_entry_sign: var

varを使用してはいけません。代わりにletconstを使用してください。

悪い例.js
var x = 1;
var y = 2;
x = 3;
良い例.js
let x = 1;
const y = 2;
x = 3;

:no_entry_sign: 関数宣言、ジェネレーター宣言 (トップレベル以外)

トップレベル以外で関数宣言、ジェネレーター宣言を使用してはいけません。何らかのブロック内で関数を定義する場合は、関数式またはジェネレーター式、アロー関数を変数に代入してください。

悪い例.js
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);
良い例.js
function 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);

ただし、トップレベルで関数宣言を使用する場合は、巻き上げ変更可能であることを十分理解していなければなりません。下記のコードの実行結果が予測ができる状態でなければなりません。

巻き上げと変更可能の影響.js
var hello = function() {
  console.log('世界!');
};
function hello() {
  console.log('こんにちは');
}
hello();

クラスおよびメソッドは、式にしなければならない理由が無い限り宣言にしてください。

クラスとメソッドは良い.js
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  isAdult() {
    if (this.age >= 20) {
      return true;
    } else {
      return false;
    }
  }
}

:no_entry_sign: コールバックに関数式

コールバックに関数式を使用してはいけません。代わりにアロー関数を使用してください。

悪い例.js
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);
良い例.js
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が明確なら良い.js
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に対する注意事項は理解した上で、という条件が付きます。

再帰関数のための関数式は良い.js
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に対する注意事項は理解した上で、という条件が付きます。

ジェネレーターがコールバック.js
const printGenerator = (g) => {
  for (const v of g()) {
    console.log(v);
  }
};
printGenerator(function*() {
  yield 'こんにちは';
  yield '世界!';
})

:no_entry_sign: コールバックにメソッド

コールバックにメソッドをそのまま使用してはいけません。アロー関数で囲ってください。

悪い例.js
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);
良い例.js
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を明示的に指定してください。

bind()を使用.js
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);
thisを指定.js
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

constructorでbind.js
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);

:no_entry_sign: for...in

for...inを使用してはいけません。配列の反復などにはfor...ofmap()reduce()等を使用してください。Objectの場合はObject.keys()を使用してください。

悪い例.js
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}`);
}
良い例.js
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()を使用することもできます。

ECMAScript2017以降.js
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}`);
}

:no_entry_sign: 配列反復にfor(;;)

配列反復にfor(;;)を使用してはいけません。for...ofmapreduce等を使用してください。

悪い例.js
const arr = [2, 3, 5, 7, 11];
for (let i = 0, len = arr.length ; i < len; i++) {
  console.log(arr[i]);
}
良い例.js
const arr = [2, 3, 5, 7, 11];
for (const x of arr) {
  console.log(x);
}

:no_entry_sign: forEach

forEachを使用してはいけません。for...ofを使用してください。また、可能な限り、mapreduce等が使用できないかを検討してください。

悪い例.js
const arr = [2, 3, 5, 7, 11];
arr.forEach(x => {
  console.log(x);
});
let sum = 0;
arr.forEach(x => {
  sum += x;
});
console.log(sum);
良い例.js
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);

:no_entry_sign: プロトタイプ作成に関数定義

プロトタイプを作成するために関数定義を使用してはいけません。クラス定義を使用してください。

悪い例.js
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());
良い例.js
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());

:no_entry_sign: undefined (保証できないとき)

undefinedがundefined値であることを保証できない限り、undefinedを使用してはいけません。確実にundefined値であることを保証するときはvoid 0を使用してください。単純にundefined値を返す場合はreturn;を使用する、undefined値であることを確認する場合はtypeofを用いて'undefined'と比較してください。存在チェック(nullまたはundefinedではない)はx != nullで十分です。

悪い例.js
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);
良い例.js
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);

:no_entry_sign: InfinityNaN (保証できないとき)

InfinityNaNが無限や非数であることを保証できない限り、InfinityNaNを使用してはいけません。確実にundefined値であることを保証するときは1/00/0など計算結果を用いてください。

悪い例.js
const Infinity = 1;
const NaN = 0;
console.log(Infinity);
console.log(NaN);
良い例.js
const Infinity = 1;
const NaN = 0;
console.log(1/0);
console.log(0/0);

:no_entry_sign: NaN (比較)

NaN(非数)のチェックに(たとえ、NaNが非数値であると保証されていても)NaNを使用してはいけません。代わりにisNaN()を使用してください。3

悪い例.js
const x = 0/0;
// xはNaNなのにfalseになる
console.log(x === NaN);
良い例.js
const x = 0/0;
console.log(isNaN(x));

:no_entry_sign: delete

deleteを使用してはいけません。Arrayはshift()splice()pop()を使用してください。連想配列はMapを使用してください。レコードはプロパティを削除しないでください。

悪い例.js
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);
良い例.js
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);

解説

:no_entry_sign: 厳格モードで削除される機能

厳格モードでは削除される機能(with文、暗黙のグローバル変数、8進数表記等)を使用してはいけません。なお、クラスやモジュールでは常に厳格モードになります。クラスやモジュールとして作成する場合は、'use strict';は書かないようにしてください。

悪い例.js
with (Math) {
  TAU = PI * 02;
}
console.log(global.TAU);
良い例.js
'use strict';
global.TAU = Math.PI * 2;
console.log(golbal.TAU);

:no_entry_sign: 暗黙のセミコロン;

文末のセミコロン;を省略してはいけません。暗黙のセミコロン;を期待してはいけません。

悪い例.js
const x = 1
console.log(x)
良い例.js
const x = 1;
console.log(x);

:no_entry_sign: {の前での改行

ブロックやObjectリテラルの開始である{の前で改行してはいけません。決してオールマンスタイルを採用してはいけません。字下げスタイルはJavaスタイルを採用してください。

悪い例.js
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());
良い例.js
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());

:no_entry_sign: returnのみの行

returnのみの行を作成してはいけません。undefined値を返したい場合はreturn;としてください。次の行が続くのであれば、()で括ってください。

悪い例.js
const f = (x) => {
  const i = 2;
  // undefinedが返る
  return
    x * i;
}
console.log(f(3));
良い例.js
const f = (x) => {
  const i = 2;
  return (
    x * i
  );
}
console.log(f(3));

:no_entry_sign: 即時関数で囲む

名前空間保護のために即時関数で囲む方法を使用してはいけません。代わりにモジュールベースで作成してください。

悪い例.js
(function(globalObj){
  function hello() {
    console.log('hello');
  }
  globalObj.hello = hello;
})(this);
良い例.js
export function hello() {
  console.log('hello');
}

解説っぽい何か

:no_entry_sign: UTF-8以外のエンコード

エンコードはUTF-8を使用し、それ以外は使用しないでください。

:no_entry_sign: ES5以前のJavaScript

ES6未対応ブラウザ(IE等)のために、ES5以前のJavaScript直接記述してはいけません。ECMAScript 2016以降(Babelでの変換が前提)を用いるか、TypeScript、CoffeeScript、LiveScript、PureScript、Opal等のAltJSで作成してください。4

:no_entry_sign: Underscore

Underscoreを使用してはいけません。ほとんどの場合は不要です。もし、どうしても必要になる場合はLodashを使用してください。

解説

:no_entry_sign: jQueryによるDOM操作

jQueryでDOM操作をしてはいけません。JavaScriptのみでjQueryと同様のDOM操作は可能です。複雑なDOM操作を行いたい場合はReactやAngularJS等を検討してください。

解説

:no_entry_sign: IE対応

IEは捨ててください。考えるだけ無駄です。

推奨編

:thumbsup: const

可能な限り変数はconstを付けて定数にしてください。変更する必要がある場合のみletを使用してください。

:thumbsup: import export

CommonJSであってもrequire()ではなくimportexportの構文を使用してください。BabelやTypeScriptを正しく設定すれば、自動的にCommonJSやAMDの形式に変換してくれます。

:thumbsup: アロー関数

関数式を使用しなければならない理由が無ければ、アロー関数を使用してください。一つの式のみでその結果を返す場合は、より簡潔に書くことができます。

:thumbsup: Airbnb JavaScript Style Guide

スタイルガイドはAirbnb JavaScript Style Guide(以下Airbnb)を使用してください。この資料のほとんどの項目はこのスタイルガイドと同じになっています。

日本語訳もありますが、内容が古くなっています。Airbnbは、より良いものを模索しているため、常に更新され続けています。最新情報はgithub上のレポジトリを確認してください。

補足

このガイドにはなぜそうするのかの理由は書いてません。理由がどうしても知りたいという方は、@ms2satoさんが書いてくれた素晴らしい補足『「JavaScript初級者のためのコーディングガイド」に補足を試みる』をお読みください5。ただし、補足を読む前に、一度自分で理由を考えてください。他人の言葉ではなく自分の言葉で説明できて初めて意味があります。それができないうちは使用禁止です!


  1. 真にJavaScriptを学ぼうとする人の事です。使おうとする人のことではありません。 

  2. この書き方はReactのマニュアルHandling Eventsで紹介されている方法です。 

  3. JavaScriptの数値はIEEE 754で64bit binary(倍精度浮動小数点数)です。この浮動小数点数には数では無い(非数)ことを表すNaNが用意されています。通常、ほとんどの実装において、NaNは何と同じかどうかの比較をしても(NaN自身と比較をしても)必ず偽になります。 

  4. ECMAScript 2016とTypeScriptについては、このコーディングガイドの通りでしょう。しかし、他のaltJSはそのままでは採用できない物もあります。それらは、それらの流儀にしたがってください。 

  5. @ms2satoさん、ありがとう!