前提
8月末から、プログラミングの学習を始めました。 学習の質を高めるため、学んだことを Qiita にアウトプットする取り組みを続けています。現在は、HTML/CSS の学習がひと段落ついたので、JavaScript の学習に入りました。
ただ、JavaScript に入ってから、学習内容がかなり本格化してきたのを感じています。
学んだことをかみ砕くのに時間がかかるので、Qiita へのアウトプットも滞りがちになってしまいました。
これじゃいかん!
ということで、急ぎ投稿したのがこの記事です。
この記事の目的
ここ数日でようやく腑に落ちてきた「ホイスティング関連」について、自分なりにまとめるのが目的です。 アップアップしながら書いているので、思わぬ間違いがあるかもしれません。 (余裕がないあまり、本文は「ですます調」ですらありません・汗)もしお読みくださった方が間違いに気づかれましたら、ご指摘いただけると大変助かります。
それでは以下、本文に移ります。
宣言文の前で呼び出せるか? その1
まずは、宣言文の前に console.log を置いた例で考えてみる。
var の場合
console.log(num);
var num=1;
この場合、console には「undefined」と出力される。
ここでの「undefined」とは、
まだ具体的な数値の入っていない「空き箱」
のようなイメージで考えればよい。
つまり、
① var 宣言文 によって「num の空き箱」が作られる
② この「空き箱」が埋まるのは、宣言文よりも後ろ
すると、「console.log(num);」は宣言文の前なので、そこで使われる num はまだ「空き箱」。
そのため、console には「undefined」と出力された。
let の場合
console.log(num);
let num=1;
この場合、console はエラーを出力する。
なぜなら、let は var と異なり、空き箱すら作らない。
そのため、宣言文よりも前では、「num など存在しない」ということになる。
したがって、「console.log(num);」はエラーを返すほかない。
const の場合
console.log(num);
const num=1;
let と同じく、console はエラーを出力する。
その理由も、let と同じ。
ここまでのまとめ
undefined だろうがエラーだろうが、うまく動いていないのは同じじゃない?
わざわざ var を封印しなくてもいいんんじゃない?
・・・とも思える。
だが、次の例を見れば「 var が非推奨になる理由」をより明確に実感できる。
宣言文の前で呼び出せるか? その2
次は、console.log よりも前に「num=500;」が書かれている例で考えてみる。
var の場合
num=500;
console.log(num);
var num=1;
この場合、console には「500」と出力される。
というのも、var によって作られた「num の空き箱」は、宣言文の前後を問わず使える。
「num=500;」の位置でも、「num の空き箱」はれっきと存在しているのだ。
その結果「num=500;」が、「num の空き箱」の中に 500 を入れる。
この 500 は、そのまま「console.log(num);」によって出力されることになる。
・・・これの何がまずいのだろう?
上記の例では、「num=500;」は宣言文からとても近い場所にある。
そのため、それほど問題はないようにも思える。
しかし仮に「num=500;」が、宣言文から10,000行離れた場所にうっかり残した消し忘れだったとしたらどうだろう?
var で宣言した変数は、コード内のいかなる場所からでも、出力結果に影響を与えることができてしまうのだ。
そう考えると、var の使用がバグの温床になりかねないことを実感できる。
let の場合
num=500;
console.log(num);
let num=1;
この場合、console にはエラーが出力される。
なぜなら、let を用いた変数宣言では、「空き箱」が作られない。
宣言文よりも前の場所では、「num という箱じたいが存在しない」ことになる。
したがって、宣言文より先に「num=500;」と書いてあったとしても、
「num って何よ??」
ということになる。
その結果、console.log はエラーを返すほかない。
このように let は、バグの温床になりかねない場面で、明確にエラーを返してくれる。
その意味で、var よりも let を使うべきなのだ。
const の場合
num=500;
console.log(num);
const num=1;
let を用いた場合と同じく、console にはエラーが出力される。
この場面では、const の挙動は let とまったく同じだ。
const との関係でも、 var はやはり非推奨ということになる。
ここまでのまとめ
基本的に、
「var は使わず、let / const を使うべき」
ということがわかった。
なぜなら、var で宣言した変数には、コードのどこからでも代入できてしまうから。
変数の予期せぬ挙動を防ぐためには、let / const を使うのが安全だということになる。
ちなみに、let / const の使い分けは?
なお、let と const の違いは「再代入の可否」。
let は再代入可能、const は再代入不可能、という違いがある。
たとえば、console に「0」「1」「2」と連続して出力されるループ処理を行いたいとする。
この場合、次のようなコードを使う。
for(let i=0; i<3; i=i+1){
console.log(i);
}
このfor 文の中には、「i=i+1」という「変数 i への再代入」の記述がある。
つまり、これは「再代入が必要なコード」である。
こうしたコードを書くには、let を使わなければならない。
const を使うと再代入ができなくなるからだ。
言い換えると、再代入の不要なコードで let を使うと、「再代入できてしまうことによるバグ」が発生するおそれがある。
結論としては、次のような方針が推奨される。
① 基本的には const を使う
② 再代入が必要な場合のみ let を使う
おまけ: 関数とホイスティングのお話(※宣言文との位置関係に関して)
これまで「宣言文との位置関係」を中心に、var / let / const について考えてきた。
こうした「宣言文との位置関係」の話は、ホイスティングとも呼ばれる。
ホスティングとは、宣言された変数・関数が、宣言文よりも前の位置で実行される挙動のこと。
宣言文よりも前に「巻き上げること(=hoisting)」が、ホイスティングという名前の由来。
つまりこれまでは、var / let / const を題材に、変数のホイスティングについて見てきたともいえる。
そこで最後に、関数オブジェクトのホイスティングについても触れてみる。
たとえば、次のようなシンプルな(シンプルすぎる)関数を考える。
function A(){
const num=1;
console.log(num);
}
A();
もちろん、コードの最後に書かれた「A();」の記述によって関数A が実行され、console には「1」と出力される。
では、このコードを次のように書き換えるとどうなるか。
A();
function A(){
const num=1;
console.log(num);
}
さきほどは最後に書かれていた「A();」が、今度は先頭に書かれている。
こうした記述でも、関数A は問題なく実行され、console には「1」と出力される。
このように関数オブジェクトは、関数宣言より前に実行文を置いても、なんら問題なく挙動する。
言い換えると、宣言文よりも前に巻き上げて(ホイスティングして)実行することができる。
ホイスティング可能な var が非推奨になった経緯を考えると、関数オブジェクトがホイスティング可能というのは、むしろ注意すべき点なのかもしれない。
今後も注意して、引き続き学習を進めていきたい。
・・・今回は、以上です。 最後までお読みくださり、ありがとうございました!