はじめに
今回はJavascriptの変数定義の話です。
他の参考記事などでよく再代入出来るかできないかの違いしか掲載していないですが、
実際はそれ以外にも違いがあり、
- javascript のホイスティングされるかどうか
- ブロックスコープが適用されるかどうか
といった違いもあり、それらの理由からvar
が非推奨になっていることを知ったので、備忘のために記事にすることにしました。
結論
ということで、まず結論としては以下のような違いがあるそうです🦈
var | let | const | |
---|---|---|---|
再代入 | 〇 | 〇 | × |
ホイスティング | 〇 | × | × |
ブロックスコープ | × | 〇 | 〇 |
よって、他の静的型付言語と同様な実行をさせたいのであれば、let
,const
のみを使えばよいvar
はホイスティングによって定義の位置が呼び出し箇所よりも後に記述しても動いてしまうため、コードの可読性を下げ、バグを生みやすくなるので扱いには注意が必要。
ホイスティングとは?
コンテキスト内で宣言した変数や関数の定義を実行前にメモリーへ配置すること
コンテキストとは?コンテキスト(Context)
という言葉の意味は「前後関係、文脈、状況、環境」といった意味がある。
ここでいう関数が実行される際の文脈や状況のことを関数コンテキスト
と言ったりする。
また、JS が実行される環境(ブラウザ内で実行される、サーバーで実行される)をグローバルコンテキスト
という
→ コードでいうと以下の箇所が関数コンテキスト
といい、また関数コンテキストの外をグローバルコンテキスト
と考えてよい
グローバルコンテキスト
ではブラウザで実行される場合、window API
等が呼び出せる
//外をグローバルコンテキストと考えてよい(実際は少し解釈が違うかもしれない)
function test() {
//ここの中のことを関数コンテキストという
}
ブロックスコープとは?{}
の中の範囲のことを指す
例としては、if 文や for 文など、関数も使うが js では別途関数スコープと呼ばれるものに区別されるため、同じではないらしい
if (flg) {
//ここの範囲
}
for (let index = 0; index < array.length; index++) {
//ここの範囲
}
では、結論の内容を詳しく説明していきます。
var
変数の再代入可能か?
→ 可能
ホイスティングされるか?
→ される
ブロックスコープ
→ 無視され、意図しない挙動になる
変数の場合
再代入
以下のよう再代入をすると値を更新することができる
var sample = 1
//再代入
sample = 2
console.log(sample)
//結果
//2
ホイスティング
ホイスティングされるため、以下のように定義の前に呼び出しを記述し、定義を呼び出しよりも後に記述してもエラーが起きることなく動いてしまう。
また、これは特に注意が必要で、定義していても初期値がundefined
で扱われてそのまま実行される
//定義の前に呼び出しを記述
console.log(sample)
//定義を呼び出しよりも後に記述
var sample = 1
//結果
//undefined
ブロックスコープ
ブロックスコープが無視されるため、以下のような記述でも動作する。
if (true) {
var sample = 1
}
console.log(sample)
//結果
// 1
関数の場合
関数の場合はvar
を使って関数式で定義した場合は上記のようなことは起きず、
代わりにfunction {関数名}
といった定義の場合に変数同様に定義の場所関係なく実行が可能。
//定義の前に呼び出しを記述
test()
//定義を呼び出しよりも後に記述
function test() {
console.log('Hello')
}
//結果
//Hello
以下の場合はTypeError: test is not a function
でエラーとなる
//定義の前に呼び出しを記述
test()
//定義を呼び出しよりも後に記述
var test = () => {
console.log('Hello')
}
//結果
//TypeError: test is not a function
let
let
に関しては変数でしか使わないので、変数での扱いを記載する
変数の再代入可能か?
→ 可能
ホイスティングされるか?
→ されない
ブロックスコープ
→ 適用される
変数の場合
再代入
正しく定義し、再代入をすると値を更新することができる
let sample = 1
//再代入
sample = 2
console.log(sample)
//結果
//2
ホイスティング
ホイスティングされない為、var の時のように定義が後の場合基本的に以下のようなエラーとなる
//定義の前に呼び出しを記述
console.log(sample)
//定義を呼び出しよりも後に記述
let sample = 1
//結果
//ReferenceError: sample is not defined
ブロックスコープ
ブロックスコープが適用されるため、以下のような記述では動作しない
if (true) {
let sample = 1
}
console.log(sample)
//結果
// console.log(sample)
// ^
// ReferenceError: sample is not defined
const
const
に関しては変数と関数で扱うため両方の扱いを記載する
変数の再代入可能か?
→ 不可
ホイスティングされるか?
→ されない
ブロックスコープ
→ 適用される
変数の場合
再代入
let
のように再代入しようとするとエラーとなる
const sample = 1
//再代入
sample = 2
console.log(sample)
//結果
// sample = 2
// ^
// TypeError: Assignment to constant variable.
ホイスティング
ホイスティングされない為、var
の時のように定義が後の場合基本的に以下のようなエラーとなる
//定義の前に呼び出しを記述
console.log(sample)
//定義を呼び出しよりも後に記述
const sample = 1
//結果
//ReferenceError: sample is not defined
ブロックスコープ
ブロックスコープが適用されるため、以下のような記述では動作しない
if (true) {
const sample = 1
}
console.log(sample)
//結果
// console.log(sample)
// ^
// ReferenceError: sample is not defined
関数の場合
以下のように定義を後に記述し、先に呼び出しを記述するとエラーとなる
ただし、var
で以下のような関数式で定義した場合のエラーとでは違うエラー内容が
表示されている
//定義の前に呼び出しを記述
test()
//定義を呼び出しよりも後に記述
const test = () => {
console.log('Hello')
}
//結果
// test()
// ^
// ReferenceError: Cannot access 'test' before initialization
以上が var
,let
,const
の違いでした
ただ単に再代入できる・できないの違いしかないと思っていたので、まさかそんなところで違いがあるのかと驚きとヤバさを感じました ⚡
var
が中々に酷い…
古いシステムとかだとvar
が使われていたりするので、既存のソースをlet
やconst
に直した際は、本当にvar
だった時の挙動を保証できているか入念にチェックした方が良いなと改めて思いました…💦
(var
だと動くみたいな良く分からないソースになってる可能性もあるかも…)
補足
上記で説明した中でエラーの内容がどうやらブラウザエンジンごとに違うらしいです。
もし試すのであれば以下2つとかで試すと良さそう。
- Chrome: V8
- FireFox: Spider Monkey
(Edge は確か Chrome と同じ V8 だったはず…)
終わりに
今回参考にさせていただいたのは、CodeMafia さんが作成された udemy の「【JS】ガチで学びたい人のための JavaScript メカニズム」という動画教材です。
ここ最近フロントエンド開発で複雑な処理を実装する機会が結構あり、
javascript の挙動や仕様で沼ったことを機に改めて勉強し直すことにしました。
その中で、個人的に覚えておくべきだなと思うことを今後も備忘録代わりに記事にしようかと考えてます
こちらで引き続き javascript の理解を深めていきたいと思います m
では、また!