本当にvarは駄目な子なのか？

やれ`let`を使え、やれ`const`を使え、と言われているES2015+時代。`var`がなぜ問題だったのか考えます。

## 他のC風言語とは違う！

JavaScript(ECMAScript)はC風言語(C-family programming languages)の一つと言われています。[List of C-family programming languages](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)の仲間であり、その中の一つとして数えられています。他にメジャーなものでは、C、C++、Java、C#、Perl、PHPあたりでしょう。PerlとPHPは変数名の規則に違いがある[^1]ので除くとしても、他の言語とJavaScriptとでは変数の取り扱い方に大きな違いがありました。

### ブロックスコープでは無い

まず、C、C++、Java、C#とES5以前のJavaScriptにはスコープに対する決定的な違いがありました。それはif文やwhile文等のブロックがJavaScriptではスコープを作らなかったと言うことです。

```c:C
#include <stdio.h>
int main(void)
{
    int i = 42;
    char *str = "hoge";
    for (int i = 0; i < 10; i++) {
        char *str = "fuga";
        printf("%s %d\n", str, i);
    }
    printf("%s %d\n", str, i); // => hoge 42
    return 0;
}
```

```js:JavaScript
var i = 42;
var str = "hoge"
for (var i = 0; i < 10; i++) {
    var str = "fuga";
    console.log(str, i);
}
console.log(str, i); // => fuga 10
```

上のCとJavaScriptでは同じようなことをしているように見えますが、最後に表示される数値が異なります。ES5以前ではfor文でブロックスコープを作らないため、`var`で宣言された`i`や`str`のスコープは全体となり、for文内と`i`や`str`も同じものであるという扱いになります。これは、C等の他のC風言語を扱っている人から見ると奇妙に思えるものでした。そこで登場したのが`let`です。

ES2015からはブロックスコープが導入され、`let`で宣言された場合は変数がブロックスコープになるようになりました。

```js:JavaScript
let i = 42;
let str = "hoge";
for (let i = 0; i < 10; i++) {
    let str = "fuga";
    console.log(str, i);
}
console.log(str, i); // => hoge 42
```

`let`で書き換えると、Cと同じ動作になることがわかります。ただし、ES2015+であっても、`var`で宣言した変数は関数スコープ(トップレベルの場合はグローバルスコープやモジュールスコープ)のままです。

勘違いしないで欲しいのは「if等でブロックスコープが作られないことが悪い」ということではありません。C風言語ではないPythonやRubyではif等でブロックスコープが作られません[^2]。ですが、if等で`{}`を使っているわけでは無いため、それほど混乱することはありません。C風言語では文のひとまとめに`{}`を使うため、これで一つのブロックを形成し、一つのまともまりを持つと考えるのが多数です。そのため、その中で宣言されたローカル変数はそのブロック内でしか使えないというのが半ば常識のように扱われていました。その常識から逸脱するJavaSriptの`var`は奇妙であり、勘違いによるバグを産み出す原因の一つになっていたと考えられます。

なお、`const`についても`let`と同様です。

### 宣言の巻き上げ

`var`には巻き上げという現象が起きます。Cと比べて見ましょう。

```c:C
#include <stdio.h>
int i = 42;
int main(void){
    printf("%d\n", i); // => 42
    int i = 2;
    printf("%d\n", i); // => 2
}
```

```js:JavaScript
var i = 42;
function main() {
  console.log(i); // => undefined
  var i = 2;
  console.log(i); // => 2
}
main();
```

二番目の`i`はCとJavaScriptとでは対象となっている`i`が違います。Cの場合はグローバル変数の`i`ですが、JavaScriptでは`main`関数のローカル変数である`i`です。JavaScriptの`var`はどこで宣言したとしても、そのスコープの最初に宣言したものと見なします。そして、初期化前であれば、暗黙的に`undefined`が入っていると見なします。つまり、下記のコードと同じです。

```js:JavaScript
var i = 42;
function main() {
  var i;
  console.log(i); // => undefined
  i = 2;
  console.log(i); // => 2
}
main();
```

サンプルのコードはすぐ近くにあるので気づけますが、関数の行数が多い場合(それ自体はあまり良いものとは言えませんが)、二番目のような`i`がトップレベルの`i`だと勘違いしてしまうかも知れません。問題なのは、このコードがエラーにならないと言うことです。

ES2015の`let`を使えば、この勘違いに即座に気づけます。

```js:JavaScript
let i = 42;
function main() {
  console.log(i); // => ReferenceError
  let i = 2;
  console.log(i);
}
main();
```

上のコードではReferenceErrorが発生し、コードは停止しますので、テストの時に気づくことができます。注意して欲しいのはCのような動作では無いと言うことです。`let`であっても宣言自体の巻き上げは発生します。しかし、暗黙的に`undefined`を入れると言うことをするのではなく、初期化前に評価されるとエラーが発生するようにしています。

なお、`const`についても`let`と同様です。

### 多重宣言

C等では同じ変数名で二重に宣言することはできません。往々にして、多重宣言は変数名の重複を意味し、どこか間違っているからです。

```c:C
#include <stdio.h>
int main(void)
{
    int i = 42;
    int i = 54; // => コンパイルエラー
    printf("%d\n", i);
    return 0;
}
```

しかし、JavaScriptの`var`では何のエラーも無くできてしまいます。

```js:JavaScript
var i = 42;
var i = 54;
console.log(i); // => 54
```

これも同じく、`let`を使えば防ぐことができます。

```js:JavaScript
let i = 42;
let i = 54; // => SyntaxError
```

なお、`const`についても`let`と同様です。

### 再代入可能

`var`には再代入を抑止する方法がありません。しかし、他の言語では再代入できないようにすることは重要なことだと考えられています。

```c:C
#include <stdio.h>
int main(void)
{
	char *const str = "hoge";
	str = "fuga"; // => コンパイルエラー
	printf("%s\n", str);
	return 0;
}
```

ES2015以降では、このような再代入禁止を行う為に`const`が用意されました。

```js:JavaScript
const str = "hoge";
str = "fuga"; // = > TypeError
console.log(str);
```

`const`は宣言時の初期化子でのみ代入が可能であり、それ以降は代入ができません。

## そして、解決したのか？

### C風に合わせただけ

色々見てきましたが、`let`や`const`がしていることは、他のC風言語と同じような動きに合わせただけと言っても過言ではありません。もっと言えば、別に`var`を使ってもうまくいっていたのです。ただ、C風な文法なのにC風の動きでは無いという所が多くのプログラマーを惑わせ、わかりにくくしていたと考えられます。

逆に言うとC風の文法で無ければ、`var`のような動きでもかまわないと考えられます。実際、PythonやRubyのローカル変数の動きは`var`に近い物がありますし、PythonとRubyの文法を参考に作られたCoffeeScriptでは、バージョン2を作るときもシンプルさを重視し、[`let`と`const`を採用しませんでした](http://coffeescript.org/#unsupported-let-const)。

JavaScriptの不幸は、すべてJavaの文字にかかっています。JavaScriptが純粋にSchemeをブラウザの世界に持ってきたものであれば、LISPのような構文になっていれば、この問題は起きなかったはずです。しかし、それを無理矢理Javaと似たもの、つまり、C風の文法にしてしまったのが、このC風言語開発者にとって奇妙に思うような`var`が産まれた理由と推測されます。一度できてしまい、ブラウザ戦争で急激に発展し、Ajaxによってブレイクして、Web周りの中心技術の一つになったとき、やっと、当時の間違いに気付いたのです。

つまり、`let`や`const`でJavaScriptが便利になったというのでは無く、やっと、C風言語の仲間入りができたというものと思われます。もっと別の文法であれば、きっとそれらは必要なかったのだと思います。

### constと言う名前は適切か？

Javaでは`final`、C#では(フィールドだけですが)`readonly`が使われています。C風とはいうのはいささか無理があるかも知れませんが、Scalaは`val`を使っています。でも、なぜ`const`なのでしょうか？

C/C++は`const`がありますが、型にかかります。`const char *s`と`char *const s`では意味が違います。JavaScriptの`const`は後者の意味に近いですが、前者の意味だと勘違いするものも多々見られます。C#にも`const`はありますが、こちらはコンパイル時定数の意味であり、Cで言えばマクロ定数、C++で言えば`constexpr`に近いものであり、さらに様相が異なります。

考えてみれば`const`の前に「定数」はとても曖昧です。「定数」という言葉は

1. 変数の評価値が定まっている。しばしば、コンパイル時に評価され、それに置き換えられている。
2. 変数が束縛されているオブジェクトの値が定まっている。
3. 変数が束縛されているオブジェクトが定まっている。

の三つの意味で使われる場合があります。JavaScriptの`const`は3.ですが、C#の`const`は1.であり、C/C++の`const`は(かかっている型に対して)2.になります。`const`という言葉はとても曖昧なような気がします。対してJavaの`final`やC#の`readonly`は3.の意味で使われます。

`final`や`readonly`、`val`といった名前の方が良かったのでは無いかと私は少し思っています。

## 結局何が言いたいか？

JavaScriptをC風言語の一つとして扱いたければ、`let`や`const`は勘違いを防ぐという意味で大変優良です。これからは積極的に使っていくべきです。ただ、一旦JavaScriptを離れ、C風言語という柵から解放されたとき、変数に三つのの種類があると言うことはどうなのだろうと思ってしまいます。過去の互換性のために`var`をなくすことはできなかったと言っても、JavaScriptの難しさが一段上がっただけのような気もしないでは無いです。


[^1]: プリフィックスとして`$`を付けるなどの違いのことです。
[^2]: Rubyではブロックを別の仕組みの用語として使っています。そちらはブロックスコープを持ちます。