JavaScriptに手を出すと最初に戸惑う3つのこと

# はじめに

プログラミング言語にはそれぞれ特徴がありますが、いろんな言語を行ったり来たりしてるとルールを忘れがちです。私がそうです。

そんな数ある言語の中から、本稿ではJavaScriptの**超基本的だけど最初は戸惑うかもしれないこと**をまとめます。

テーマは3つ。

1. ECMAScriptとは
2. 変数宣言の仕方
3. 厳密等価演算子

私はこれまでJavaScriptはちょこちょこ使う程度で、使う度に「あれ？これどうだったっけ、、、（Google先生に聞く）」というルーティンがありました。こうなったものを備忘録的に残して、**「もう忘れない」**という強い意志をここに記したいと思います。

# 1. 「ES6？ES2015？？」

JavaScriptに関することをググっていると、高確率でESとかECMAScriptという文字を見かけます。
私は「**ESってなんやねん、エントリーシートか？**」と疑問を持ったことを覚えています。
そもそもの部分ではありますが、JSを理解する上で重要だと思うので、改めて確認しておきましょう。

## ES(= ECMAScript): JavaScriptの標準仕様

**ES**とは、**ECMAScriptの略称**で、そのECMAScriptとは、[Ecma International](https://www.ecma-international.org/)という国際的な標準化団体が策定した、**JavaScriptの標準**のことを指します。

標準化に際しての経緯や、とあるバージョンは途中で放棄しちゃったりなど、いろいろと歴史があって面白いのですが、長くなるのでここでは割愛します。

[ECMA-262](https://www.ecma-international.org/publications/standards/Ecma-262.htm)という規格番号で標準化されていますが、度々改訂が行われています。
その改訂が行われたタイミングで版番号が更新され、ES1から始まり2、3、4、5、6、…と続いている、**わけではなく**、このナンバリングはES5までで（厳密にはマイナーバージョンの5.1）、その先からは「毎年改訂しちゃうよーん:smirk::metal:」と張り切ったため、発行年が名前にくっつくことになっています。
つまり、実質ES6にあたるバージョンから**ECMAScript 2015**と呼ぶことにしたのです。
ただし、ナンバリングの呼び方が消滅したわけではなく、今でも呼びやすいからという理由でES6という名前は使われています。

ということで、ES6とES2015は同じものを指します。
じゃあ、ES6って呼ぶのが間違いなのかというとそうでもないみたいで、「正式に呼ぶなら発行年呼びのほうがいいなぁ」くらいのニュアンスみたいです。

> ES6 is colloquial shorthand for “ECMA-262, Edition 6”.  ES6 was published as a standard in 2015. The actual title of the ES6 specification is ECMAScript 2015 Language Specification and the preferred shorthand name is ECMAScript 2015 or just ES2015.
> [Some ECMAScript Explanations and Stories for Dave](http://www.wirfs-brock.com/allen/posts/778)より

## 2020年12月現在の最新版ECMAScript

2020年6月に改訂されたバージョン11、**ECMAScript 2020**が最新版です。

もちろん[2021](https://tc39.es/ecma262/)も着々と仕様策定が進んでおり、今時点で確定したものは以下の通り。

- String.prototype.replaceAll
- Promise.any
- WeakRefs
- Logical Assignment Operators
- Numeric separators

うーん、楽しみですね（適当（白目（勉強しなきゃ（焦り））））

---

# 2. 「var？let？const？」

私は変数宣言といったら、`int`とか`string`とか**静的型付け**したくなるのですが、世の中には**動的型付け**なる便利（そう）な型付けも存在します。

しばしば、静的型付け言語派閥 vs 動的型付け言語派閥の宗教戦争が勃発するのは有名な話です。

動的型付けとは、プログラム実行前に型を決めておくことをせず、実行されたときの実際の値を見て型を決めるというものです。

動的型付け言語で有名なものは

- Python
- PHP
- Ruby
- LISP

などなど、いろいろあります。

何を隠そうJavaScriptもその動的型付け言語の一つで、型をあんまり意識しなくてもちょろちょろ〜っと書けちゃうわけです。

では、実際にちょろちょろ〜っと書いてみましょう。

:blush:「よっしゃ！足し算させてみよか」

:rolling_eyes:「ふむふむ…[MDN Web Docs](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Grammar_and_types)によると、宣言の仕方は`var`、`let`、`const`の3つか…」

:relieved:「『*`var`は変数を宣言し、ある値に初期化することもできる。*』…じゃあこれでいっか」

```javascript:足し算.js
var augend = 1;
var addend = 2;

console.log(augend + addend);
```

:blush:「できた！！！！」

:no_good_tone1:「**`var`は推奨されてへんで**」

:muscle::baby:「？？？？？」

## 3つある宣言

JavaScriptの変数宣言は3つ存在し、前述の通り、**`var`**、**`let`**、**`const`**です。

結論をいうと、**イチオシは`const`**です。
ちなみに、`const`で宣言する際は、必ず初期値を代入する必要があることは覚えておきましょう。

それぞれの特徴を表でまとめると以下の通り。

| |var|let|const|
|--:|:-:|:-:|:-:|
|再代入|○|○|×|
|再宣言|○|×|×|
|ブロックスコープ|×|○|○|

ブロックスコープという書き方が若干曖昧ですが、つまり、`{}`で囲んだところをブロックを呼び、その中で`let`もしくは`const`で宣言された変数はそのブロックの中だけで有効、`var`はその宣言されたブロックを含む関数内もしくはグローバルスコープで有効ということです。

```js:スコープ例
if (true) {
  var a = 1;
  let b = 2;
}

console.log(a);    // 1
console.log(b);    // Uncaught ReferenceError: b is not defined 
```

上記の例では、ブロック内で`a`は`var`、`b`は`let`で宣言されており、前述の通り`let`で宣言するとブロック内のみで有効な変数となりますので、ブロックの外で`b`を出力しようとしても「bは未定義だよー」とReferenceErrorが吐かれます。

こうやってみてみると、`var`は宣言方法を3つ並べたときに1番最初に紹介されるし、JavaScript関係の解説サイトとかでも割と使われているし、再代入できるし、再宣言もできるし、スコープ関係ないしで、使いたくなっちゃうところですが、残念ながら**varは非推奨**です。

[ブラウザの対応状況](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/let#Browser_compatibility)によっては、`let`と`const`が使えないこともあるかもしれませんが、そんな化石プラットフォームを使っている白亜紀在住で移動手段が恐竜みたいな方々のサポートは投げ捨てていきましょう。

## 宣言は`const`で

`let`は、`var`の代わりといってもいいでしょう。

:kissing_smiling_eyes:「じゃあ、`let`を使えばいっか」

という考えは許されません。極刑に値します。**斬首です**。

なぜならば、"代わり"ということは**`let` ≒ `var`**だからです。
`var`が非推奨なのに`let`が無条件に許されるわけがありません。

| const javascript 使い方 | 検索 |
でググると大抵こんな記事を見つけます。

> 中身の値を変えられたくないもの、変えないことが明らかであるものに関しては、値の書き換えが禁止されている`const`を用いると良いですよ（胡蝶しのぶスマイルを添えて）

`const`さんをそんな脇役みたいな言い方しやがって……！！

**『特別な理由がない限り、全ての変数は`const`で宣言するべき』**です。

仮に、古の時代に`var`で宣言していた部分を全て`let`で宣言したとして、間違いなく「可読性が上がった」と言えるでしょうか？

可読性の観点からいえば、再代入が行われる、もしくは行われる可能性があるコードは好ましくありません。あとからそのコードを見る人は、「**値が変わるかもしれないから注意して読もう**」と余計な労力をかける必要があるためです。

`const`であれば、再代入されることはないので、この無駄な手間とドキドキをなくすことができます。

### `const`は『改変できない』ではない

一つ注意すべき点は、`const`は**再代入できない**のであって、**値を改変できない**のではないということです。

みんな大好き[MDNのドキュメント](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/const#Description)には以下のように書かれています。

> const 宣言は、値への読み取り専用の参照を作ります。これは、定数に保持されている値は不変ではなく、その変数の識別子が再代入できないということです。たとえば、定数の中身がオブジェクトの場合、オブジェクトの内容（プロパティなど）は変更可能です。

ちょっと何言ってるかわからない状態なので、順を追って説明をしていきます。

#### プリミティブ型とオブジェクト型

JavaScriptのデータは、大別すると**プリミティブ型**と**オブジェクト型**に分けられます。

プリミティブ型とは、メソッドを持たないデータのことで、以下の6つの型があります。

- String（文字列）
- Number（数値）
- BigInt
- Boolean（真偽値）
- undefined
- Symbol

オブジェクト型は、配列やmapなど、プリミティブでないものを指します。

#### 変数の持ち方

変数に入っているデータは、その変数に代入した値が格納された領域の**参照値**となります。
JSの変数はC言語とかのポインタ的な感じ、と私は理解しています。（実際は違うっぽいですが）

例えば、`const a = 1;`という文が実行されたとき、中では以下のように処理がなされます。

1. 変数`a`に値を格納するための領域が割り当てられる
2. その領域に値を格納する
3. 変数`a`に値を格納した領域の参照値(ref1)を入れる

![Address.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924363/2c84a520-5683-df72-2020-365d9c74e622.jpeg)

これはプリミティブ型もオブジェクト型も同様です。

#### `const`は参照値の変更を禁止している

`const`で宣言した際のからくりについてお話します。

まず、プリミティブ型について。

```js
const a = 1;

a = 10000;
```

変数`a`に入っているのは「1」という数値を格納した領域の参照値です。
![Address (4).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924363/b200b9db-3c11-aad2-c733-20271e86d6a3.jpeg)

再代入するために`a = 10000;`とすると、今度は「10000」という数値を格納した領域の参照値を変数`a`に入れようとします。
`const`で宣言されていた場合、この変数に入れた**参照値の変更ができない→結果として再代入できない**ということになるのです。
![Address (5).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924363/52c9cc72-1f9d-76d6-5edd-06a7e0b63dab.jpeg)

ちょっとだけややこしく感じがちなのが、オブジェクト型。

```js
const obj = [1, 2, 3];
obj = ['a', 'b', 'c'];    // Uncaught TypeError: Assignment to constant variable.
```

これはもちろん怒られます。`['a', 'b', 'c']`という配列が格納された参照値を代入しようとするためです。

しかし、以下の場合は違います。

```js
const obj = [1, 2, 3];
obj[0] = 'a';

console.log(obj);    // ['a', 2, 3]
```

これはバッチリ`obj`の中身が書き換えられています。なぜならば、`obj`の格納先の参照値は変更していないからです。
あくまで代入したのは、配列オブジェクト`obj`のインデックス0の部分に対してなので、`obj`の配列が格納されている領域は変わっていません。もちろん`push()`や`pop()`などオブジェクトに対するメソッドも問題なく使用できます。

![Address (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924363/b080748a-cc89-c9d3-9a7c-6817329b7d7c.jpeg)

![Address (3).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/924363/0fa3ee14-9e7f-11d8-790b-3ca7720737b6.jpeg)

> 定数の中身がオブジェクトの場合、オブジェクトの内容（プロパティなど）は変更可能です。

が意味することは、こういうことなんですね。

## `var`は非推奨

さて、`var`は事実上非推奨です。それでも残っているのは、昔のコードとの互換性のためでしょう。
では、なぜ`var`は非推奨なのでしょうか。

理由としては、以下の2つがあげられます。

1. 変数の中身をころころ書き換えられたり、何回も宣言されるとワケワカメになる→バグ
2. 変数巻き上げ発生時がややこしや→バグ

要するに、**コードの可読性を上げてバグを少なくしましょうね〜**ということなんですね。

### 変数の書き換え

特に長いコードを書いているときに変数を`var`で宣言すると、前に宣言したのにもう1回宣言してしまったり、if文などのブロック内で操作した値を再代入したことで関数全体に影響を与えてしまったりなど、意図しない動きを生じさせてしまう可能性があります。
`let`や`const`を使っておけば、意図しない変数書き換えが発生しづらくなるでしょう。

### 独特な挙動 『変数巻き上げ』

「初学者が戸惑うこと」の一つとして大項目で紹介しようかとも思いましたが、小項目にランクダウンした『巻き上げ』くんです。

**変数の巻き上げ**とは、宣言された変数は文の先頭または関数の先頭で宣言されたとみなされるというものです。

:yum:「は？」

よくわかりませんね。例を示しましょう。
以下のようなコードを実行します。

```js
var a;
console.log(a);
```

コンソールには**`undefined`**が出力されます。
これは、初期値なしで`var`や`let`で宣言した場合には`undefined`という値をとる、というルールに基づくものです。

それでは、以下のように、宣言と出力が逆になるとどうでしょう。これを実行すると何がコンソールに出力されるでしょうか。

```js
console.log(a);
var a = 1;       // 宣言が後
```

正解は**`undefined`**です。

:yum:「は？？」

つまり、以下のコードと同様の動きをするということです。

```js
var a;    // 先頭で宣言
console.log(a);
a = 1;
```

:yum:「は？？？？？」

と言われてもこれが『変数の巻き上げ』なので、こうなのだと理解するしかありません。あしからず。
関数内の変数も同様に、宣言された関数の先頭に"巻き上げ"られます。

```js
var a = 1;

var func = () => {
  // var a;             <- このように宣言されているのと同じ
  console.log(a);    // 出力: undefined
  var a = 2;
};

func();
```

さて、巻き上げの説明が長くなりましたが、`var`の非推奨話と何が関係あるかというと、`var`で宣言すると仮に巻き上げが発生して`undefined`になってもそれに気づかず処理が進んでしまい、**バグを誘発させる**恐れがあります。
一方、`let`や`const`で宣言しておくと、巻き上げが発生すると**参照エラー**となり、コードの実行が止まります。

```js
console.log(a);    // ReferenceError
const a = 1;
```

よって、結果論的ではありますが、`let`や`const`で宣言すれば意図しない巻き上げによるバグを防ぐことができる、ということになるわけです。
そもそもですが、巻き上げが発生するようなコードを書くと**センス/Zero**と思われるのでやめたほうがいいです。たぶん。

どうでもいいけど、このエラーが発生する領域をTemporary Dead Zoneっていうんですって。なんかカッコいいからとりあえず書いておきますね。

#### 余談： 関数の巻き上げ

関数宣言に関しても同様に巻き上げが発生します。

```js
chosenOne('Anakin');

function chosenOne(name) {
  if (name === 'Anakin') {
    console.log('You were the chosen one!');
  }
}
```

これは、関数宣言が行われる前に関数を呼び出しているように見えますが、ちゃんと関数が実行されます。
関数の巻き上げが発生しているからです。

厳密に言えば、巻き上げが発生するのは**関数宣言**です。
つまり、以下のような**関数式は巻き上げられない**ということです。

```js
chosenOne('Anakin');    // エラー

const chosenOne = function(name) {
  if (name === 'Anakin') {
    console.log('You were the chosen one!');
  }
};
```

---

# 3. 「等価演算子は`=`2つだよ（決めつけ）」

```js:edo.js
const edoEst = answerYear => {
  const edoEstYear = '1603';

  if (edoEstYear === answerYear) {
    console.log('正解!');
  } else {
    console.log('不正解...');
  }
};
```

:open_mouth:「おっ、こんなところに江戸時代が始まった年のクイズ関数が宣言されてる。興奮してきたな。ちょっと答えてみよ」

```js:回答
edoEst(1603);    // 出力: 不正解...
```

:confounded:「あれ？1603年じゃないっけ？？おかしいなぁ」
:frowning:「あ、`edoEstYear`と`answerYear`を比較してるところの`=`が**3つ**になってるよ…タイポだろうなぁ直しとこ」

```js
// 省略
  if (edoEstYear == answerYear) {
// 省略

edoEst(1603);    // 出力: 正解!
```

:blush:「よしよし、『正解!』って出たな！」

:cop_tone1:「……」

## 等価演算子は`=`3つのものもある

`===`でもちゃんと条件分岐して『不正解』を出していたわけですから、タイポではないことは明確ですが、なぜ正しい値である『1603』を入力しても不正解と判定されたのでしょうか。

`===`は**厳密等価演算子**といいます。
つまり、`==`は**厳密じゃない等価演算子**ということになります。

厳密じゃない等価演算子は、比較対象の型が異なる場合には型の変換をして**型をあわせてから比較を行う**賢い子です。
つまり、以下のこれらは全部`true`になります。すごい。

```js
1 == '1';
0 == false;
null == undefined;

const number1 = new Number(3);
number1 == 3;
```

一方、`===`は「**型が同じなら比較する**よ、異なるもん持ってきたら中身も見ないよ」とツンツンした子です。
つまり、上に示した例は全て`false`が返ってくることになります。

先程の江戸の例に戻ると、正答である`edoEstYear`の宣言文を見ると

```js:edoEstYear宣言部分
const edoEstYear = '1603';
```

右辺はシングルクオーテーションで囲んであり、文字列で定義されていることが分かります。

しかし、解答としてedoEst関数の引数を

```js:回答
edoEst(1603);
```

数値で渡しており、厳密等価演算子による比較を行うと型が異なるため、比較結果が*false*、『不正解』が返ってきました。

（厳密等価演算子はPHPにもありますが、）他のプログラミング言語を扱っていると、何も考えずに`==`を使って比較しても、比較する値の型が異なればコンパイル時などにエラーが吐かれたりして、間違って使っても気付きやすかったりします。
JavaScriptにおける`==`は便利ですが、便利ゆえに問題に気付きにくくハマる原因になる可能性があります。

正確に意図したものを作るためには、型まで厳密に比較してくれる`===`を使うほうがプログラムの意図も伝わりやすくなり、バグも少なくなることが期待されるので、比較する際は`=`3つの方も思い出して一考してみるべきでしょう。

ちなみに、等価の逆、`!==`厳密非等価演算子も存在するのでこちらもあわせて覚えておきましょう。

# まとめ

基本的なことだけど、知らないと戸惑うことをまとめてみました。

記事をまとめるにあたって、自分で調べながら再度確認しましたが、「あーそうだったっけ」とか、「そーなんだ！」というものがあり、非常に身になりました。
この記事を読んだ方にとっても、有意義な時間になったことを願います。

一つ一つのボリュームがありすぎて、それぞれ1記事にできそうなレベルのものを一つの記事にまとめたためかなり長めになってしまいました。
自分が理解できるレベルまで落とし込むのと同時に、尚且読んでて面白いと思われるような記事を書きたかったため、逆に分かりづらかったり噛み砕いた表現が多かったりしたかもしれませんがご了承ください。

# 参考文献

- [JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript)
- [Standard ECMA-262](https://www.ecma-international.org/publications/standards/Ecma-262.htm)
- [ECMAScript® 2021 Language Specification](https://tc39.es/ecma262/)
- [ECMAScript Finished Proposals
 | GitHub](https://github.com/tc39/proposals/blob/master/finished-proposals.md)
- [Some ECMAScript Explanations and Stories for Dave](http://www.wirfs-brock.com/allen/posts/778)
- [The Abstract Equality Comparison Algorithm | ECMAScript Language Specification](http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3)
- [The Strict Equality Comparison Algorithm | ECMAScript Language Specification](http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.6)