Javascriptのコーディングでハマったところ

# はじめに

Javascriptのハマりポイントを後輩に教えるために書きます。


# 変数定義

var, let, const(定数) の３種類ある。

可能な限り const を使いましょう。どうしても必要な時だけ let を使いましょう。

const の方が Typescript での型推論も正確に働いてくれます。

var は古い書き方です。変数のスコープが分かりにくいので使用しないでください。

```javascript
// { から } がブロックになる

if (true) {
    var x = 'var宣言'
}

console.log(x) // x が参照できちゃう。let or const だとエラーになってくれる。
```

### スコープについて

let, const はブロックスコープです。つまりブロック内で定義した変数はそのブロック内でのみ有効です。

ブロックの外からブロック内の変数は参照できませんが、ブロック内からブロック外の変数は参照可能です。

ブロックは`マジックミラーの部屋`のようなイメージを持つと良いです。中から外は見えるけど、外から中は見れない。そんなイメージです。

### 何も指定しなくてもコード動くんだけど？

指定しなかったら`var`として動作してしまいます。ちゃんと指定しましょう。

# プリミティブ型とオブジェクト型の扱いについて

以下は全てプリミティブです。これ以外の配列、オブジェクトリテラル、関数などは全てオブジェクト型です（多分）

- 文字列
- 数値
- 論理値
- 長整数
- undefined
- シンボル
- null

## だから何？

### 「再代入」と「変更」

プリミティブ型では値を変更するためには let で定義して再代入する必要があります。const で定義すると再代入ができないので変更できません。

オブジェクト型では const で定義すると（let と同じように）再代入はできませんが要素の変更はできます。

```javascript
const s = 'string'
s = 3 // constだからダメ

const a = [1, 2, 3]

a = [4, 5, 6] // constだからダメ

// constでも要素の変更はできる
a.push(4) // [1, 2, 3, 4]
```

### 値の比較

プリミティブ型は直感的に分かりやすいですね。

```javascript
const x = 0
const y = 0
if (x === y) {
    console.log('x === y') // 実行される
}
```

オブジェクト型の比較は注意が必要です。以下の`a`と`b`は「別のオブジェクトを比較している」と判定され、`false`になります。

```javascript
const a = [1, 2, 3]
const b = [1, 2, 3]
if (a === b) {
    console.log('a === b') // 実行されない
}
```

#### どうしても`true`にしたいんだけど？

オブジェクト自体をそのまま別の変数に代入して比較すれば`true`になります。

```javascript
const a = [1, 2, 3]
const b = a
if (a === b) {
    console.log('a === b') // 実行される
}
```

ただし、こんなことする機会はありません。バグが生まれやすくなるだけです。ぜひ以下を読んでください。

### 意図していないオブジェクト型の変更

```javascript
const a = []
const b = a

b.push(100)

console.log('a:', a) // -> a: [100]
console.log('b:', b) // -> b: [100]
```

これは恐らく意図していない動作でしょう。`a`と`b`は異なる配列のように見えますが、同じ配列への参照を持ってしまっているからです。

`b`に対して行なった変更は全て`a`にも適用されます。逆もまた然りです。

このようなことはプリミティブ型では起こりません。

#### 対策

~~先ほどのようなコピーはShallowCopy（浅いコピー）と呼ばれます。~~ <- 要確認

配列への参照をコピーしただけで、値自体は同じものを指している状態です。

```javascript
const a = []
const b = a
```

DeepCopyを使いましょう。Javascriptではこう書きます。

```javascript
const a = []
const b = [...a]
```

これで `b` は `a` と全く異なる（異なる参照をもつ）配列になりました。もう `b` への変更が `a` にも反映されることはありません。

#### もっと見つけにくい場合もある

誰かが配列をソートするすごい関数を作ってくれたとしましょう。ぱっと見よさそうですね。

```javascript
// 配列をただソートして返すだけの関数
function mySuperSort(array) {
    const sortedArray = array.sort()
    return sortedArray
}

const data = [9, 8, 7, 6, 5, 4, 3, 2, 1]

const result = mySuperSort(data)
console.log(data) // -> [1, 2, 3, 4, 5, 6, 7, 8, 9]
```

何故か変数`data`もソートされてしまっています。ちょっと`sort()`の[ドキュメント](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)を参照してみましょう。

- sort() メソッドは、配列の要素をその場でソートし、ソートされた同じ配列の参照を返します。

実は、`data` と `array` は同じ参照を持っています。つまり `array.sort()` が実行した時点で `data` もソートされてしまったということです。

結果的に、`data`と`result`も全く同じオブジェクトを指してしまっている（同じ配列の参照を持っている）ことになります。

試しに比較してみると true を返します。

```javascript
console.log(data === result) // -> true
```

これもDeepCopyを使えば対策できます。

```javascript
function mySuperSort(array) {
    const _array = [...array]
    const sortedArray = _array.sort()
    return sortedArray
}
```

# 値の比較

`!=` と `==`は使用しないでください。必ず `===` か `!==` を使ってください。



```javascript
if (2 == "2"){
    console.log('2 == "2"') // 実行される
}

if (2 === "2"){
    console.log('2 === "2"') // 実行されない
}
```

# 関数の定義

Javascriptの関数の定義方法は３つあります。

以下の３つの関数は全く同じ動作をします。

```javascript
// 通常の関数
function myFunc(x) {
    return x
}

// アロー関数
const myArrowFunc = (x) => {
    return x
}

// アロー関数定義 + return の省略
const myArrowFunc2 = (x) => x
```

# 繰り返し

### 配列

```javascript
const items = ["a", "b", "c"]

// これは古い書き方
for (let i = 0; i < items.length; i++) {
    console.log(i)         // 0 -> 1 -> 2
    console.log(items[i])  // "a" -> "b" -> "c"
}

// in は index を返す
for (const i in items)
    console.log(i)  // "0" -> "1" -> "2" 何故か文字列

// of は要素を返す
for (const item of items)
    console.log(item)  // "a" -> "b" -> "c"

// indexも要素も使いたい
for (const [i, item] of items.entries()) {
    console.log(i)     // 0 -> 1 -> 2
    console.log(item)  // "a" -> "b" -> "c"
}
```

### オブジェクトリテラル

```javascript
const obj = {
    "k1": 1,
    "k2": 2,
    "k3": 3
}

for (const k of Object.keys(obj)) {
    console.log(k)       // "k1" -> "k2" -> "k3"
    console.log(obj[k])  // 1 -> 2 -> 3
}

for (const v of Object.values(obj))
    console.log(v)  // 1 -> 2 -> 3

for (const [k, v] of Object.entries(obj)) {
    console.log(k)  // "k1" -> "k2" -> "k3"
    console.log(v)  // 1 -> 2 -> 3
}
```

# 同期処理と非同期処理

### 説明

皆さんは「プログラムはコードの行順に実行される」という認識を持っていると思います。それは同期処理と呼ばれるもので、処理中のコード行が完了してから次の行に進むものです。

非同期処理においては上から順に処理されるとは限らず、例えばコードの上の方で渡しておいた関数がしばらく経ってから実行されるようなパターンがあります。

### 抽象的に説明

イメージとしては、「この作業やっといてね。終わったら呼んで。俺は別の作業するから。」とお願いする感じです。

料理の注文で言うと、

- 食券を買う
- 店員に食券を渡し、料理を作り始める (関数の実行開始)
- 空いた席で好きに自分の時間を過ごす (次の行に進む)
- 料理ができたので店員が呼ぶ (コールバック関数の実行)
- 料理を取りに行って食べる (戻り値を受け取って処理を行う)

という流れです。(合ってるのかなこれ…？)

「同期処理と比べて何がいいの？」と思ったあなた、鋭いです。では料理の注文の例において、お客さんが100人いたとしましょう。

同期処理だと

- １人目が食券を渡す
- 料理を作る
- 料理を渡す
- ２人目が食券を渡す
- 料理を作る
- …

となります。１つずつしか料理しないので、厨房がいくら広くても（CPU、メモリが高性能でも）無駄が多いです。

後ろの方に並んでいるお客さんは怒って帰るでしょう。「何故暇そうにしている店員や使っていないコンロを使わないんだ？待ってるんだよこっちは」と。

非同期であれば100人分の食券を一度に受け取って、早い順にお客さんへ料理が提供できます。厨房もフル稼働です。

### 例

ちょっと脱線しましたが、例を示します。

特に以下のような「応答を待つ」処理は非同期処理で実装されます。

- HTTPリクエスト
- ファイル入出力
- DBへのアクセス
- ユーザーからの入力を待つ（これに出会う頻度は少ないかも）

> 正確には`Python`だろうが`C#`だろうが非同期処理の仕組みはあります。
> 
> javascriptは「絶対に避けて通れない」という意味で異なると思います。

### Promiseを使った非同期処理（非推奨）

これは [Promise](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise) を使って非同期処理を実装したものです。

`DBへのアクセス` -> `HTTPリクエスト` -> `ファイル出力` の順に処理を行います。

> `dummy.*` で始まる関数は説明のための仮想的な関数です。

```javascript
function main() {
    console.log('main() start')

    // DBを読んで…
    dummy.readDb().then((dbData) => {
        console.log('readDb() completed.')
        console.log(dbData)

        // データが１件以上あればそれをサーバーに送って…
        if (dbData.length > 0) {
            dummy.fetch(dbData).then((httpCode) => {
                console.log('fetch() completed.')

                // 成功したならローカルファイルに書き出す
                if (httpCode === 200) {
                    dummy.fileWrite(dbData).then((result) => {
                        console.log('fileWrite() completed.')
                    })
                }

            })
        }
    })

    console.log('main() end')
}


console.log('promise.js start')

main()

console.log('promise.js end')

```

このコードを実行するとコンソールには以下の順で出力されます。

```bash
promise.js start
main() start
main() end
promise.js end
readDb() completed.
[
  { id: '1', email: 'a@a.com', name: 'bob' },
  { id: '2', email: 'b@b.com', name: 'mike' },
  { id: '3', email: 'c@c.com', name: 'richards' }
]
fetch() completed.
fileWrite() completed.
```

`dummy.*` で始まる関数に引数として渡している`コールバック関数`（アロー関数のところです）が遅れて実行されています。

なんとなく分かりにくいコードであることが分かるでしょう。

特に変数 `dbData`, `httpCode`, `result` の値を参照するためには変数があるブロックと同じか、それより内側のブロック出なければならないため非同期関数が入れ子に（ネスト）なってしまっています。

これが俗にいう「コールバック地獄」と呼ばれるものです。これはまだマシな方で、上のコードに例外処理や判定が追加されるともっと酷いことになります。

コールバック関数を使うことなく同期処理っぽく書けて、変数のスコープも分かりやすい書き方があればなぁ…ということで以下の書き方をしてください。

### async, awaitを使った非同期処理

Promiseの諸々の使いにくさを解消するためにも、可能な限り `async`, `await` を使った実装にしてください。非同期処理を同期処理であるかのように記述できます。

まずは今まで`function`で定義していた関数を`async function`と記述する非同期関数に変えます。

`await`は非同期関数の中でしか使えません！

そしてPromiseを返す関数、メソッドに`await`を付けて待機しましょう。コードは以下のようになります。

```javascript
async function main() {
    console.log('main() start')

    // DBを読んで…
    const dbData = await dummy.readDb()
    console.log('readDb() completed.')
    console.log(dbData)

    // データが１件以上あればそれをサーバーに送って…
    if (dbData.length === 0) return // -> 抜ける
    const httpCode = await dummy.fetch(dbData)
    console.log('fetch() completed.')

    // 成功したならローカルファイルに書き出す
    if (httpCode !== 200) return // -> 抜ける
    const result = await dummy.fileWrite(dbData)
    console.log('fileWrite() completed.')

    console.log('main() end')
}

console.log('await-async.js start')

main()

console.log('await-async.js end')
```

変数 `dbData`, `httpCode`, `result` が `main` 関数内ならどこでも参照できることにも注目してください。

実行してみます。`main() end`が表示される位置が違うくらいで、ちゃんと非同期処理が実行されます。

```bash
await-async.js start
main() start
await-async.js end
readDb() completed.
[
  { id: '1', email: 'a@a.com', name: 'bob' },
  { id: '2', email: 'b@b.com', name: 'mike' },
  { id: '3', email: 'c@c.com', name: 'richards' }
]
fetch() completed.
fileWrite() completed.
main() end
```

### Promise、よく分かりません…

最初は難しいです。私はここをみて一気に理解できました。おすすめです。

-> [JavaScript Promiseの本](https://azu.github.io/promises-book/)

### Promiseを返す関数、メソッドがどれなのか分からないんですけど…

VSCodeなどのIDEで関数、メソッドにカーソルを合わせてみてください。

試しに `readDb()` にカーソルを合わせるとこんな感じのが表示されると思います。

```typescript
(alias) readDb(): Promise<any>
```

この、「`:`」より後ろの部分のが戻り値の型を表しており、これがPromiseであればawaitで待機できます。

### おまけ

参考に、`dummy.*`の実装はこんな感じです。

```javascript
// DBの読み込みダミー。２秒待つ。
async function readDb() {
    const dbData = [
        { id: '1', email: 'a@a.com', name: 'bob' },
        { id: '2', email: 'b@b.com', name: 'mike' },
        { id: '3', email: 'c@c.com', name: 'richards' },
    ]

    await new Promise((resolve) => setTimeout(resolve, 2000));
    return new Promise((resolve) => {
        resolve(dbData)
    });
}

// HTTPリクエストダミー。２秒待つ。
async function fetch(data) {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    return new Promise((resolve) => {
        resolve(200)
    });
}

// fp.writeダミー。２秒待つ。
async function fileWrite() {
    await new Promise((resolve) => setTimeout(resolve, 2000));
    return new Promise((resolve) => {
        resolve('fileWrite() success!')
    });
}

module.exports = {
    readDb,
    fetch,
    fileWrite,
}
```

# その他補足など

### 分割代入とスプレッド演算子

よく使う便利な機能として[スプレッド構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax) と分割代入があります。

どうしても説明が長くなるので一度調べてみてください。

### Typescriptについて

できるだけTypescriptで実装してください。

javascriptで大規模なシステムを組むことは（多分）ほとんどありません。

特に新規開発でjavascriptを採用することは事情がない限りないでしょう。

`.js`拡張子を`.ts`にするだけで型推論の恩恵が受けられるほどTypescriptは素晴らしいです。

# 是非この記事のコメントに目を通してください。