Javascriptのコーディングスタイルを復習 - Airbnb(ES5)編


前回は、[Google Javascript Style Guide](http://cou929.nu/data/google_javascript_style_guide)を眺めて、重要そうなトピックを抜き出しました。

今回は、[Airbnb javascript Style Guide (ES5)](https://github.com/mitsuruog/javascript-style-guide/blob/master/es5/README.md)を眺め、重要そう・かつ忘れそうなトピックを抜き出していきます。

なお、Google Style Guideで明示されている部分についても省略します。

前回と同様にサンプルコードは原文通りではなく、理解促進のために簡潔な形に修正しています。

### オブジェクト
- オブジェクトのキーに予約語を使用しない（IE8で動作しない）  
利用する場合には、分かり易い同義語を採用する。

```javascript
/* bad */
var obj = {
  class: 'hoge'
}

/* good */
var obj = {
  klass: 'hoge',
  type: 'hoge'
}
``` 

### 配列
- 配列をコピーする際はArray#sliceを使用する。

```javascript
var arr = [0, 1, 2];

/* bad */
var arrCopy = [];
for (i = 0, len = arr.length; i < len; i++) {
  arrCopy.push(arr[i]);
}

/* good */
arrCopy = arr.slice();
```

- Array-likeなオブジェクト(主にarguments)をArrayに変換する場合は、Array#sliceを使用する。

```javascript
function trigger() {
  var args = [].slice.call(arguments);
}
```


- 文字列連結をループ内で行わない

```javascript
var items,
	messages,
	length,
	i;
	
messages = [
  {message: 'this one'},
  {message: 'this two'},
  {message: 'this three'},  
];

length = messages.length;

/* bad */
function inbox() {
  items = '<ul>';
  
  for (i = 0; i < length; i++) {
    items += '<li>' + message[i] + '</li>';
  }
  
  return items + '</ul>';
}

/* good */
// 文字列の配列をループで作成してからjoinで連結
function inbox() {
  items = [];
  
  for (i = 0; i < length; i++) {
    items[i] = messages[i].message;
  }
  
  return '<ul><li>' + items.join('</li><li>') + '</li></ul>'
}
```

### 関数
- パラメータに`arguments`を指定しないこと。  
関数ブロック内に渡される`arguments`の参照が上書きされるため。  
可変長引数を利用出来る事を明示したい場合は`var_args`を引数に配置する。（関数内での利用はあくまでも`arguments`）

```javascript
/* bad */
function foo(name, opt_foo, arguments) {
 var args = [].slice.apply(arguments);
 return args;
}

/* good */
function foo(name, opt_foo, var_args) {
  var args = [].slice.apply(arguments);
  return args;
}
```

### プロパティ
- プロパティへのアクセスは`.`を利用する。
- 変数を使用してプロパティにアクセスする場合は`[]`を利用する。

```javascript
var obj = {
  foo: 1,
  bar: 2
}

// 基本は.でアクセス
var foo = obj.foo;

// 変数を利用する場合は[]でアクセス
function getProp(prop) {
  return obj[prop];
}
var foo = getProp('foo');

// 演算を行いたい場合も[]でアクセス
var arr = ['foo', 'bar'],
	index = 0;
var bar = arr[index + 1];
```

### 変数
- 複数の変数を宣言する場合は、`var`を一つだけ記述して変数毎に改行する。
- 未定義変数は最後に宣言する。  
定義済み変数を代入したい場合などに便利。

```js
/* bad */
var i;
var len;
var foo = 'foo';

/* good */
var foo = 'foo',
    i = foo,
    len;
```

- 変数の割当はスコープの先頭で行う（主に関数スコープ）  
なお、変数の割当前に必要となる処理はこの限りではない。

```js
/* bad */
function foo(var_args) {        
  if (!arguments.length) {
    return false;
  }
  
  if (name == 'foo') {  
    return bar;
  }
    
  var name = arguments[0];
  var bar = bar();
}

/* good */
function foo(var_args) {  
  if (!arguments.length) {
    return false;
  }
  
  var name = arguments[0],
      bar = bar();
  
  if (name == 'foo') {
    return foo;
  }
}
```

### 巻き上げ
関数や変数の巻き上げについて詳細に解説されていましたが、既知の内容だったため、ここでは関数内における関数式を代入した変数の巻き上げについてのみ取り上げます。
(無名関数も名前付き関数も同じ挙動)

```javascript
function foo() {
  console.log(bar); // -> undefined
  
  // 関数割当前の変数が巻き上げられる
  // 関数の表示名と実行名の内、表示名が巻き上げられる
  // 表示名のみでは実行出来ないため、例外が発生する
  bar(); -> TypeError bar is not a function
  
  var bar = function bar() {
    console.log('bar');
  }
}
```

忘れた時に参照したいセクションです。
[巻き上げ](https://github.com/mitsuruog/javascript-style-guide/blob/master/es5/README.md#巻き上げ-原文)

### ブロック
複数業のブロックには`{}`を利用する。  
なお、`function`は必ず改行する。

```javascript
// bad
if (test)
  return false;

// good
if (test) return false;

// good
if (test) {
  return false;
}

// bad
function() { return false; }

// good
function() {
  return false;
}
```

### コメント
- コメントは該当処理の上部に記述  
また、単一行コメントの前には空白行を配置

```javascript
// bad
var active = true;  // is current tab

// good
// is current tab
var active = true;

// bad
function getType() {
  console.log('fetching type...');
  // set the default type to 'no type'
  var type = this._type || 'no type';

  return type;
}

// good
function getType() {
  console.log('fetching type...');

  // set the default type to 'no type'
  var type = this._type || 'no type';

  return type;
}
```

- 問題の指摘や解決策の提案を行う必要があるコードには`TODO, FIXME`を記述する。
	- TODO: 実装が必要(解決策の提示もこちら)
	- FIXME: 解決策が必要

### 空白
- タブはスペース2つ
- 中括弧`{}`の前にはスペース1つ
- 制御構文の丸括弧`()`の前にはスペース1つ（関数の引数リストの前には入れない）

```javascript
// bad
if(isJedi) {
  fight ();
}

// good
if (isJedi) {
  fight();
}

// bad
function fight () {
  console.log ('Swooosh!');
}

// good
function fight() {
  console.log('Swooosh!');
}
```
- 演算子の間にはスペース1つ
- ファイルの最後には改行文字を入れる
- メソッドチェーンが長くなる場合は、論理的なまとまり毎に適宜インデントを入れる

```javascript
// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();

// good
$('#items')
  .find('.selected')
    .highlight()
    .end()
  .find('.open')
    .updateCount();

// bad
var leds = stage.selectAll('.led').data(data).enter().append('svg:svg').class('led', true)
    .attr('width',  (radius + margin) * 2).append('svg:g')
    .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
    .call(tron.led);

// good
var leds = stage.selectAll('.led')
    .data(data)
  .enter().append('svg:svg')
    .class('led', true)
    .attr('width',  (radius + margin) * 2)
  .append('svg:g')
    .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
    .call(tron.led);
```

### カンマ
- 先頭にカンマを入れない
- 末尾に余計なカンマを入れない

### セミコロン
- トップレベルの匿名関数・名前空間オブジェクト前後には`;`を入れる  
(ファイル連結時のリスク対応)

### 型変換
- 文字列の型変換は式中の先頭で行う  
不要な記述が増える場合はこの限りではない

```javascript
//  => this.reviewScore = 9;

// bad
var totalScore = this.reviewScore + '';

// good
var totalScore = '' + this.reviewScore;

// bad
var totalScore = '' + this.reviewScore + ' total score';

// good
var totalScore = this.reviewScore + ' total score';
```

- 数値の型変換は`Number`もしくは基数を添えた`parseInt`で行う

```javascript
var inputValue = '4';

// bad
var val = new Number(inputValue);

// bad
var val = +inputValue;

// bad
var val = inputValue >> 0;

// bad
var val = parseInt(inputValue);

// good
var val = Number(inputValue);

// good
var val = parseInt(inputValue, 10);
```

- parseIntがボトルネックとなる場合はwhy,whatのコメントを添えたビット演算子を利用

```javascript
  // good
  /**
   * parseIntがボトルネックとなっていたため、
   * ビットシフトで文字列を数値へ強制的に変換することで
   * パフォーマンスを改善させます。
   */
  var val = inputValue >> 0;
```

- 真偽値の型変換は`Boolean`もしくは`!!`を利用

```js
var age = 0;

// bad
var hasAge = new Boolean(age);

// good
var hasAge = Boolean(age);

// good
var hasAge = !!age;
```

### 命名規則
- 1文字の名前は避ける（最低限の意味を読み取れる長さにする）
- privateプロパティには`_`を接頭辞として付与

```js
// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';

// good
this._firstName = 'Panda';
```

- thisの参照を保存する場合は`_this`

```js
// bad
function() {
  var self = this;
  return function() {
    console.log(self);
  };
}

// bad
function() {
  var that = this;
  return function() {
    console.log(that);
  };
}

// good
function() {
  var _this = this;
  return function() {
    console.log(_this);
  };
}
```

- スタックトレースを追い易くするために、関数には名前をつける  
(コールスタック表示時に`anonymous function`と表示されないようにする)

```javascript
// bad
var log = function(msg) {
  console.log(msg);
};

// good
var log = function log(msg) {
  console.log(msg);
};
```
**[IE8では名前付き関数を関数宣言と認識する等のバグが存在](http://d.hatena.ne.jp/think49/20110521/1305959222)**

- プロパティが真偽値の場合`isVal(), hasVal()`とする

```javascript
// bad
if (!dragon.age()) {
  return false;
}

// good
if (!dragon.hasAge()) {
  return false;
}
```

### コンストラクタ
- prototypeにプロパティを追加する際は、既存のプロトタイプをオーバーライドしない。

```js
function Foo() {}

// bad
Foo.prototype = {
  bar: function bar() {},
  baz: function baz() {}
};

// good
Foo.prototype.bar = function bar() {};
Foo.prototype.baz = function baz() {};
```

- プロトタイプチェーンを積極的に活用

```javascript
Foo.prototype.bar = function bar(x) {
  this.x = x;
  return this;
};

Foo.prototype.baz = function bar(y) {
  this.y = y;
  return this;
};

var foo = new Foo();

foo
  .bar('bar')
  .baz('baz');

console.log(foo.x, foo.y); // -> bar baz
```

### イベント
イベントのコールバックに渡すペイロードは、オブジェクトや配列でラップして、イベント・ペイロード変更時の影響範囲を減らすこと

```javascript
// bad
$(this).trigger('fooEvent', param.id);

$(this).on('fooEvent', function(e, paramId) {
  // do something with paramId
});

// good
$(this).trigger('fooEvent', { id : param.id });

$(this).on('fooEvent', function(e, data) {
// do something with data.id
});
```

### モジュール
- モジュールは`!`で始める  
[単項演算子を先頭に付与するとfunction式として評価されるため](http://blog.tojiru.net/article/197270788.html)
- モジュールのファイル名はキャメルケースで記述して、同じ名称のフォルダに格納
- `noCOnflict()`という名称で名前衝突して上書きされる前のモジュールを返すメソッドを追加
- モジュールのスコープの先頭で`'use strict';`を宣言

```js
// fancyInput/fancyInput.js

!function(global) {
  'use strict';

  var previousFancyInput = global.FancyInput;

  function FancyInput(options) {
    this.options = options || {};
  }

  FancyInput.noConflict = function noConflict() {
    global.FancyInput = previousFancyInput;
    return FancyInput;
  };

  global.FancyInput = FancyInput;
}(this);
```

### JQuery
[JQuery高速化について](http://millkeyweb.com/jquery-high/)

- JQueryオブジェクトの変数は先頭に`$`を付与
- DOMの検索結果はキャッシュする
- DOMの検索にはカスケードを利用する  
`$('.sidebar ul')`や`$('.sidebar > ul')`
- DOMの検索にはスコープ付きの`find`を利用  
**検索結果はキャッシュする、メソッドはキャッシュされた変数に対して利用する、疑似要素をセレクタに含めない**というルールが適切かも。


次回はES6のスタイルを含む[Airbnb Javascript Style Guide](https://github.com/mitsuruog/javascript-style-guide)やReact.js周りのスタイルを含む[Airbnb React/JSX Style Guide](https://github.com/airbnb/javascript/tree/master/react)を眺めたいと考えています。