🗳️

JavaScript学習:変数編|varはなぜ非推奨?let, constとの違い

    JavaScript学習記録|なんとなく理解禁止シリーズ

昔からある変数宣言方法であるvarですが、ECMAScript2015(ES6)から新たにlet, constが導入されてから、開発者の中でもvar宣言は非推奨とされるようになりました。

そもそも、let, constが追加された理由はvarの問題点を改善するため。JavaScriptやECMAScriptについて詳細に説明がされている JavaScript Primer にもvarの問題点とES6でlet, constが追加された経緯が説明されています。

この学習記録では、varがなぜ非推奨か改めて整理しようと思います!

【前提】JavaScriptの変数宣言の種類

まず初めに、変数宣言のパターンとそれぞれの特徴を簡単にまとめます。

宣言方法再宣言再代入スコープ初期化
var関数undefined
letXブロックX
constXXブロックX

それでは早速、varの問題点と非推奨な理由を下記に分けてまとめていきます!

  • ホイスティング(変数の巻き上げ)時にundefinedで初期化される
  • 関数スコープであることの問題
  • 同じ変数名での再宣言が可能
  • グローバルスコープの変数を上書きできる

ホイスティング(変数の巻き上げ)時にundefinedで初期化される

下記のようなコードを書いたときに、varとletではエラーの有無に違いが出てきます。

console.log(a);
console.log(b);
var a = 1;
let b = 1;

/* console.log(a)の結果
undefined
*/
/* console.log(b)の結果
Uncaught ReferenceError: Cannot access 'b' before initialization
*/

letの場合はホイスティングの際に、変数宣言文より前に変数を参照すると「初期化前に変数にアクセスしている」というReferenceErrorが出るようになっています。でもvarの場合は、undefinedと表示されるだけでエラーになりません。

開発者は変数aは1を返すと考えて組んでいても、undefinedになっていることに気づかず、意図しない事態になりかねないので問題という訳です。

関数スコープであることの問題

これは書いて見たほうが分かりやすいので、letと比較して一度適当なコードを書いてみます。

function a () {
  if (true) {
    let b = 1;
    var c = 1;
  }
  console.log(b);
  // 結果:Uncaught ReferenceError: b is not defined
  console.log(c);
  // 結果:1
}
a();

letはブロックスコープなので、参照できる範囲は上記のソースならif文の中に限られます。でも、varは関数スコープなのでfunction a関数の範囲であればどこからでも参照できてしまいます。これがもし、if文と変数cを参照する記述の間に何百行も空いていたら流石に気づきづらく、これまた意図しない事態になる事があるので、非推奨な理由の一つです。

同じ変数名での再宣言が可能

varは下記のように同じ名前での再宣言を許してしまいます。

var a = 1;
var a = 1;

letであれば下記のようなエラーを出してくれます。

let a = 1;
let a = 1;
// 結果:Uncaught SyntaxError: Identifier 'a' has already been declared

既に同じ名前で宣言されていることに気づかず、再宣言して上書きする可能性があって問題ですが、さらにグローバルオブジェクト(ブラウザ上でのwindow)と合わせて考えるとより一層問題となります。下記の見出しで書いていきますね。

グローバルスコープの変数を上書きできる

まず前提として、関数宣言された関数やvar宣言された変数は、グローバルオブジェクト(ブラウザ上ではwindow)に値が格納されます。

var a = 1;
function b () { console.log('hello') }
console.log(window.a);
// 結果:1
window.b();
// 結果:hello

前の問題点で書いた通り、var宣言は再宣言が可能です。なので、下記みたいな事ができてしまいます。

innerWidth = 10;
console.log(window.innerWidth);
// 結果:10

innerWidthは画面の横幅を取得できる元々windowに組み込まれている変数ですが、その変数を再宣言して上書きできてしまいます。当然開発者の知らないwindowプロパティもあるはずで、知らずに独自の変数として宣言して、windowのプロパティと競合してしまう事態になりかねません。このような意図しない結果を生む可能性があるのでvar宣言は問題ありです。