JavaScriptで知っておいて損はないこと

この記事は[OthloTech Advent Calendar 2016](http://qiita.com/advent-calendar/2016/othlotech)の6日目の記事です。

![othlotech-logo-1.png](https://qiita-image-store.s3.amazonaws.com/0/85665/7f550f22-1e57-5ed9-ae8a-78e2a91f8d2d.png)

こんにちは！ OthloTechのぽこひでです。最近はJavaScriptを良く書いているので、[JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス](https://www.amazon.co.jp/gp/product/4873113911/ref=as_li_ss_tl?ie=UTF8&camp=247&creative=7399&creativeASIN=4873113911&linkCode=as2&tag=kenjuw-22)でも紹介されてた良いパーツや悪いパーツの中から開発の上で知っておいて損はないことをまとめました。

この記事は、JavaScript初中級者向けの記事です。

## JSのオブジェクトは参照渡し
JavaScriptのオブジェクトはコピーではなく参照渡しです。なので、以下のようなことが起きます。

```js:同じオブジェクトを参照
var x, y; x = y = {};   // x, yは同じ空オブジェクトを参照している。
x['hoge'] = 1;          // xを更新すると
y['hoge']               // 1
```

また、同様にJavaScriptでの`=`は単なる値渡しではなく、オブジェクトの場合は値のポインタ、つまり参照の値渡しがされます。

```js:オブジェクトの参照先を参照
var a = {'hoge': 1};
var b = a;             // これはオブジェクトがコピーされるのではなく、参照先がコピーされただけ
a['hoge'] = 2;         // なので参照先の値を更新すると
b['hoge']              // 2
```

これを防ぐには、`=`を用いたコピーではなく、独自で`clone`メソッドなどを定義するのも良いのですが、なかなか面倒くさいので[node-cloneモジュール](https://github.com/pvorb/node-clone)を用いたりします。

## 配列の長さ
~~JavaScriptの配列にはサイズの上限がなく、~~`length`プロパティは配列内の`最も大きな整数値をもつプロパティのプロパティ名 + 1`なので、以下のような動作になります。

```js:配列の長さ
var array = [];
array.length      // 0
array[100] = true;
array.length      // 101(100 + 1)
```

なので、基本的に配列の操作は添字で指定するより、あらかじめ提供されている`pop`や`push`等の標準APIをなるべく使用すべきです。

### 訂正
JavaScriptの配列の`length`プロパティは`Unit32`の範囲と定められているので、整数値であってもその範囲から溢れれば、`length`に影響しないことがあります。

```js
var arr = [];
arr[10] = "a";
arr[-1>>>0] = "b"; // lengthは 2^32 (=4294967296) を持てないので、これをlengthで表現できない
console.log(arr.length); // 最初に格納したもののみが適用されて、 lengthは11
```

```js
new Array((-1>>>0) + 1);
// 因みにArray ConstructorではUint32を超える引数を渡すと例外が飛ぶ
```

コメントをくださった[gaogao_9](http://qiita.com/gaogao_9)ありがとうございます。

## 配列のソート
JavaScriptの配列の`sort`メソッドは数値の配列を正しく並び変えることができません。

```js:正常でない数配列のソート
var array = [32, 12, 5, 2, 7, 10];
array.sort();
// [ 10, 12, 2, 32, 5, 7 ]
```

JavaScriptの標準の比較関数は、比較の前に変数の型を調べないため、どの要素も文字列と見なしてソートしてしまうので、正しくソートされません。

なので、自分で比較関数を与えてやることで回避できます。比較関数は2つのパラメータを受け取って、それらが等しいなら0を、1番目のパラメータが先なら負の数を、逆なら正の数を返すようにすれば独自でカスタマイズできます。

```js:正常な数配列のソート
var array = [32, 12, 5, 2, 7, 10];
array.sort((function(a, b) {
  return a - b;
});
// [ 2, 5, 7, 10, 12, 32 ]
```

もっと独自にオブジェクト配列をこのプロパティでソートしたい！と思ったら[JavaScript つい忘れてしまう配列のソート方法](http://qiita.com/PianoScoreJP/items/f0ff7345229871039672)とか見てみるといいかもしれないです。

## `parseInt`
`parseInt`は文字列を整数に変換するメソッドですが、数字以外の文字が見つかると処理をストップしてしまうので、注意が必要です。なので、

```js:parseIntの挙動
parseInt('7')    // 7
parseInt('7km')  // 7
```

となります。また、文字列の頭が`0x`だと16進数とみなされたりするので、知っておいて損はないと思います。第二引数に基数を指定できるので、特別なことがない限り明示的に書くといいでしょう。

```js:parseIntの挙動2
parseInt('0x12')      // 16進数なので、18 (16*1 + 1*2)
parseInt('0x12', 10)  // 10進数なので、文字列でストップし 0
```

## 浮動小数の誤差
JavaScriptは8バイトの[IEEE 754](https://ja.wikipedia.org/wiki/IEEE_754)という浮動小数点標準という規格を使用して浮動小数点数を表現していて、それに収まらない場合は丸めなどを行っているため浮動小数点の計算は必ずしも正しいとは限りません。
ちなみに、8バイトなのでJSでは浮動小数点数を32個の`[0,1]`で表現していて精度的には10進数桁数でいう7桁程度です。

```js:浮動小数点数の計算による誤差
0.1 + 0.2   // 0.30000000000000004
0.14 * 100  // 14.000000000000002
```

これを防ぐ方法をいくつか。

### 数値のスケーリング
浮動小数同士の計算は誤差を含みますが、整数演算は正しく行われるので、`10^n`を掛けて整数に一旦直すことで避けれます。

```js:数値のスケーリングによる誤差回避
(0.1 * 10 + 0.2 * 10) / 10  // 0.3
(0.14 * 100 * 100) / 100    // 14.000000000000002
```

しかし、この場合でも整数値にスケールアップするときに小数点の計算をしているので、結果的に誤差が残ってしまいます。

### 外部ライブラリに頼っちゃう
[`decimal.js`](https://mikemcl.github.io/decimal.js/)や[`bignumber.js`](https://github.com/MikeMcl/bignumber.js/)、[`math.js`](https://github.com/josdejong/mathjs)などのライブラリを頼って頑張って下さい(´･ω･`)

この問題はJavaScriptに限らずほとんどの言語で言えることなので知っておいて損はないですね。

## `NaN`とかいう奴
`NaN`は、上記のIEEE 754で定義された`Not a Number`の頭文字をとった数値を持たない特別な値です。なのになぜか

```js:numberやん
typeof NaN  // 'number'
```

「いやいや`Number`じゃん(*-∀-)つ ﾅﾝﾃﾞﾔﾈﾝ！」

ってなりますよね。ちなみに、`NaN`は前述の`parseInt`などで数字の形式になっていない文字列を変換するときに生成されます。

```js:NaNはいつできるか
parseInt('hogehoge', 10)  // NaN
Number({})                // NaN
```

これを判定するのに、JavaScriptでは`isNaN`関数を提供しているのですが、これは`parseInt`などのメソッドを使用したときに引数が数値に変換可能かどうかの判定をしているようなので、以下のような動作をします。

```js:isNaN関数の挙動
isNaN(NaN)       // true
isNaN('hoge')    // true
isNaN(undefined) // true
isNaN({})        // true

isNaN(0)         // false
isNaN('1.234')   // false
isNaN(null)      // false
// ちなみに、Number(null) は 0なのでfalseです...
```

これ以外にも`NaN`には自分自身との等号判定で`false`を返す性質があるため、他の型とは違って以下のような動作をします。

```js:NaNだけおかしい
0 === 0           // => true
'hoge' === 'hoge' // => true
null === null     // => true

NaN === NaN       // => false
```

なので、[Effective JavaScript](https://www.amazon.co.jp/Effective-JavaScript-JavaScript%E3%82%92%E4%BD%BF%E3%81%86%E3%81%A8%E3%81%8D%E3%81%AB%E7%9F%A5%E3%81%A3%E3%81%A6%E3%81%8A%E3%81%8D%E3%81%9F%E3%81%8468%E3%81%AE%E5%86%B4%E3%81%88%E3%81%9F%E3%82%84%E3%82%8A%E6%96%B9-David-Herman/dp/4798131113)ではこの性質を利用して、以下のようなイディオムを紹介していたようです。

```js:NaNであるかを判定するメソッド
function isReallyNaN(x) {
  return x !== x;  // xがNaNであればtrue, それ以外ではfalse
}
```

## `===` と `==`
基本的には`==`でも正常に動作するのですが、これは比較対象が同じデータ型のときのみです。もし、比較対象の型がバラバラであった場合、`==`は両者の型を強制的に統一してから判定を行うため以下のようなことが起き得ます。

```js:==のおかしな挙動
'' == 0    // true
0 == ''    // true
0 == '0'   // true
false == 0 // true
```

この例から、`==`演算子は前者と後者のデータ型が違った場合は、前者のデータ型に後者を強制的に変換して判定していることがわかります。このように、`==`演算子は予想と反する結果が表示するので、データ型も考慮する`===`を使いましょう。

## グローバル変数
生のJavaScriptはグローバル変数に依存しています。気をつけましょう。

## さいごに

### `ECMAScript6` を使いましょう!
`ECMAScript6`で何ができるかはこの[いまからはじめるECMAScript 6](http://www.slideshare.net/1000ch/begin-ecmascript6)のスライドが分かりやすかったです。(以下、略して`ES6`と表記します)

今、上記で説明したJavaScriptの**知っておいて損はないこと**を知っておいて損はないのですが、`ES6`は上記の普通のJavaScriptに対して文法や機能が追加されていて、簡単な例だと`const`や`let`が使用できます。

```js:varの重複定義
var x = 100;
var x = 200;
console.log(x);  // 200
```

このように上書きできてしまいます。これを必ず開発を進めていく上でバグを生むのですが、`let/const`を使うと

```js:letを重複定義
let x = 100;
let x = 200;  // SyntaxError: Identifier 'x' has already been declared
```

```js:constを重複定義
const x = 100;
const x = 200; // SyntaxError: Identifier 'x' has already been declared
```

というように重複定義がエラーになったり、他にも変数のスコープについても、`let/const`の場合は関数以外の構文内もプライベートなスコープ空間とみなして、構文の外を汚染しません。

このように、いろいろとメリットがあるので、余力があれば導入しましょう。

### `ES6`の導入
ただし、`ES6`には少し問題があります。`ES6`には

- `let`, `const`キーワードによる変数宣言
- `class`キーワードによるクラス宣言
- 関数の引数のデフォルトパラメータ
- アロー関数
...

などいろいろと新しい文法が追加されているのですが、全てのブラウザ上でそのコードがそのまま動くわけではないので、`Gulp`や`Webpack`, `Babel`などを用いて、どのブラウザでも動くJSに変換する必要があります。

- [フロントエンドでもES6構文使ってみる webpack+babel-loader](http://yutapon.hatenablog.com/entry/2015/02/09/090000)
- [ES6 で書く環境を作る（gulp + Babel 編）](http://qiita.com/hkusu/items/9b312274b3ff2913e15b)
- [gulp+ES6でフロントエンド開発](http://qiita.com/tyfkda/items/92dccc14aa7c05f0d3bc)

この辺りを参考にして頑張ってみてください！笑

`ES6`の構文などのまとめは以下のリンクが詳しいので、興味を持った方は見てみてください。

- [ES2015 (ES6)についてのまとめ](http://qiita.com/tuno-tky/items/74ca595a9232bcbcd727)
- [もうはじめよう、ES6~ECMAScript6の基本構文まとめ(JavaScript)~](http://qiita.com/takeharu/items/cbbe017bbdd120015ca0)

後半は少し雑になってしまいましたが、これを機に[**OthloTech**](http://www.othlo.tech)という東海の学生テック団体の存在を知っていただけると嬉しいです！！
あと、悪いコードは撲滅しましょうσ(ﾟ∀ﾟ)