JavaScriptにおける変数巻き上げ(ホイスティング)

JavaScriptには関数スコープしか無い為、気をつけないと意図しないで既存の変数を書き換えちゃうかもって話です。

そもそもホイスティングって何?

冒頭でも書いた通り、JavaScriptには関数スコープしかありません。また、同じ変数を同じスコープの中で複数回宣言するのは違法とはなりません。その為、

function trimSections(header, body, footer) {
  for (var i = 0, n = header.length; i < n; i++) {
    header[i] = header[i].trim();
  }
  for (var i = 0, n = header.length; i < n; i++) {
    body[i] = body[i].trim();
  }
  for (var i = 0, n = header.length; i < n; i++) {
    footer[i] = footer[i].trim();
  }
}

のようなコードを書いた時、6個のローカル変数(iが3つ、nが3つ)を宣言したつもりでも、実際には下記のコードと等価となります。

function trimSections(header, body, footer) {
  var i, n;
  for (i = 0, n = header.length; i < n; i++) {
    header[i] = header[i].trim();
  }
  for (i = 0, n = header.length; i < n; i++) {
    body[i] = body[i].trim();
  }
  for (i = 0, n = header.length; i < n; i++) {
    footer[i] = footer[i].trim();
  }
}

ローカル変数i,nの宣言がスコープの先頭(関数定義の先頭)に移動しているのが分かると思います。先頭に持ってくるという意味を込めて、この挙動は「変数巻き上げ(ホイスティング)」と呼ばれています。

で、上記のコードなんですが... forループを回す為だけの変数が共有される形となってしまい、何となく危うい感じがしますよね?上の例では同期的な処理しかしてないので大丈夫ですが、非同期処理がからむと大変な事になる予感がします。

また、このページで紹介されていた下記のようなコードも大変ミスリーディングです。

var a = 0;
(function() {
  console.log(a); // undefinedが出力される!!!
  var a = 2;
  return a;
})();

直感的にはconsole.log0が出力されそうではありますが、実際にはundefinedが出力されます。なぜかと言えば、上記のコードは下記のコードと等価になるからです。

var a = 0;
(function() {
  var a;
  console.log(a); // aは宣言だけされて初期化されてないのでundefined
  a = 2;
  return a;
})();

var a;という宣言文だけがホイスティングによって関数スコープの先頭にやってくる訳ですね。非常にやっかいな仕様だと思います。

結論

ホイスティングはやっかいなので気をつけましょう。とは言っても、こんな面倒な仕様を気にしなきゃいけないのもバカらしいので、CoffeeScriptとか使っときましょう。var宣言を自分で書く必要がなく、Coffeeのコンパイラが上手い事やってくれます。ループとかもいい感じにやってくれます。ループに関しては、前回紹介したunderscore.js使うんでもいいです。

追記: どうでもいい話

最近、JavaScriptの細かく面倒な仕様をブログに書いてますが、自分としては皆が細かい部分を覚えるべきとは思っていません。というより、ハマりポイントが多数あるのは言語側の落ち度なので、本来なら言語自身でどんどん改善されていくべきだと思います。

ただ、JavaScriptはブラウザで動く言語であると言う特性上、どうしても古いバージョンとの互換性を気にする必要があります。その為、どんなにクソっぽい仕様であっても、今更変更するのは難しかったりします。(例えば、for inは絶対に自身のプロパティのみイテレーションする実装の方が良かったと思います。)

で、この問題の解決策についてなんですが、言語の上に一つレイヤーを重ねてやればいいんだと思います。つまり、面倒な仕様や落とし穴なんかはフレームワークやaltJSで上手くラップして、一般のユーザーは細かい事を気にせずにプログラミング出来るようになればいいと思います。実際、そういった方向でいろんなツールが作られていたりするので、僕としてはとっても良い事だなと思っています。

Effective JavaScript JavaScriptを使うときに知っておきたい68の冴えたやり方

Effective JavaScript JavaScriptを使うときに知っておきたい68の冴えたやり方