Edited at

JavaScript var/let/constによる変数宣言

プログラム未経験の知人にJavaScriptを教えていた所、「varとletとconst、何が違くて、どう使い分ければいいのかわからない」と言われ、私なりにもう一度深く調べたので、上記についてまとめます。


var(変数)

再宣言・再代入が可能で、古いブラウザでも動く(ES5以下でも動く)。

スコープは関数スコープで、以下のようなコードが書ける。

function (){

var x = 0;
console.log(x); //0が出力
var x = 1;
console.log(x); //1
x = 2;
console.log(x); //2
if (true){
console.log(x); //2
x = 3;
var y = "a";
}
console.log(x); //3
console.log(y); //a
}

function (){
console.log(x); //error x is not defined
}

関数スコープなので、varで定義した変数はその関数内でなら呼び出し可能で、再定義var x = 1;や、再代入x = 2;が可能。

if文の中で値を変更して、そのブロックを抜けても値は保持されたまま。

もちろん、2つ目の無名関数は変数xのスコープにないのでエラーが発生します。


let(変数)

再宣言は不可、再代入は可能。大体のブラウザで動くが、IE9とかだと動かない。(ES6以上でないと動かない)。

Babelとか使うと古いブラウザにも対応するコードに変換してくれるが、今回の趣旨とは違うため説明はしない。

スコープはブロックスコープ。以下のコードを参照。

function(){

let x = 0;
console.log(x); //0
let x = 1; //error x has already been declared
console.log(x); //0
x = 2;
console.log(x) //2
if (true){
console.log(x); //2
x = 3;
let y = "a";
}
console.log(x); //3
console.log(y); //error y is not defined
}

function (){
console.log(x); //x is not defined
}

1つ目の無名関数内で定義した変数xは、そのブロック内にスコープがあるので、if文の中もスコープ範囲であること、再宣言が不可であることがわかります。

そしてif文の中で定義した変数yはif文のブロック内にしかスコープを持たないので、その外の関数では使えないことがわかります。

もちろんこちらも、2つ目の無名関数内ではエラーが起こります。


const(定数)

再宣言、再代入が不可で、ブロックスコープ。

letが動くならconstも動く(ES6以上)。以下のコードを参照。

function (){

const x = 0;
console.log(x); //0
const x = 1; //error x has already been declared
console.log(x); //0
x = 2; //error Assignment to constant variable.
console.log(x); //0
if (true){
console.log(x); //0
const y = 3;
}
console.log(y); //error y is not defined
}

function (){
console.log(x); //error x is not defined
}

このように、再宣言、再代入をしようとするとエラーが起こってしまいます。

関数型言語などでは、そもそも再代入不可がデフォルトのものもあったり、再代入するとバグを生みやすくなるよと警告を出してくれるものもあります。ただし、JavaScriptで重要な概念の一つにオブジェクトというものが存在します。

constを使うと、再宣言再代入が不可という特徴から、constで定義されたものは絶対に変化しないと思いがちになりますが、オブジェクトを操作することは可能です。具体的には以下のコードが書けるということです。

function (){

const like = {fruit: "apple", movie:"GODZILLA", food:"soba"};
console.log(like); //{fruit: "apple", movie: "GODZILLA", food: "soba"}
like.food = "rice";
console.log(like); //{fruit: "apple", movie: "GODZILLA", food: "rice"}
like = {fruit: "grape", movie: "GODZILLA", food: "rice"}; //これはNG
}

言語によって様々な機能や、ちょっとした特性など、知らないとバグの要因になってしまうことは沢山あります。

自身がよく使う言語の特徴はしっかり把握しておきましょう。


変数の巻き上げ

恐らく、JavaScript特有の概念だと思いますが、変数宣言に関連した挙動で、変数の巻き上げ(hosting)というものが存在します。

例えば、このコードを見てください。(@Kobecow さんのコメント通りに修正しました)

var x = 0;

function (){
console.log(x);
var x = 1;
console.log(x);
}

一つめのconsole.log(x);では、何が出力されると思いますか?

普通に考えていくと、0が出力されるように見えますが、実際はundefinedが表示されます。

0でもなく、error x is not definedでもなく、undefinedです。

これがJavaScriptの巻き上げという挙動なのですが、簡単に説明すると、JavaScriptは関数が宣言された時点で、その中でvarで宣言されてる変数をすべてundefinedで初期化するというものです。

その結果、上記コードだと、思いもしない結果が出力されるわけです。

また、varが再宣言再代入可能で、letは再代入のみ可能だという挙動も、この変数の巻き上げによるものです。

function (){ //ここでxとyがundefinedで初期化される。varで変数宣言されているから。

var x = 0;
var y = 1;
let z = 2; //zはここで初期化される。
}

実際、大体の言語は同名の変数を、その名前空間の中で新しく宣言するのは原則として禁止されています。

JavaScriptはvarのみ、関数の頭で初期化されているので、var x = 0;は再宣言というより、コード内部では再代入として扱われているわけです。


使い分け

基本的にはvarは使わないで、constを使い、後々再代入するならletを使いましょう。

そして命名規則ですが、constはプロジェクトや自信の好みによってというのもありますが、基本は大文字で、単語ごとにアンダーバーで区切りましょう。(アッパーケース)

変数は最初の単語は小文字で、2つ目から頭を大文字にして書くことをオススメします。(キャメルケース)