【JavaScript】変数の宣言・初期化・参照について

前回の投稿から1年以上経ってしまいました。

現在UdemyのとあるJavaScriptの講座を見ています。 分かったつもりで進めてもこの先さらにつまずくだろうなと思ったので、忘れがちな部分をまとめました。

変数の宣言・初期化

変数の宣言・初期化・そして代入というのはメモリ上ではどういう状態?という疑問が出発点です。
そこにホイスティングが加わるとややこしいなと思ったので、以下検証しました。

let, const 宣言

変数宣言のキーワードである letconst には、変数が呼び出せる範囲、スコープがあります。

JavaScript講座第2章 基本文法 | DevDojo

letconst の特性です。

let

  • ブロックスコープ: let で宣言された変数はブロックスコープを持ちます。これは、{} で囲まれた範囲内でのみその変数が利用可能であるということを意味します。
  • letで宣言された変数も巻き上げられますが、初期化前のアクセスはエラーとなります。

const

  • ブロックスコープ: constもブロックスコープを持ちます。
  • 再代入不可: 一度値が代入されると、その後の再代入はできません。
  • オブジェクトのプロパティの変更は許可されます。

ここからは const よりもややこしい let について考えます。
var は非推奨で使わないので、思い切って割愛します。

先に変数を呼び出す

宣言や初期化をしていない変数(一部は変数ともいえないただの文字)を真っ先に呼び出すと下記のようになりました。

※この図ともう一つ下の図は、検証外のパターンをコメントアウトしつつまとめたものです。一気にこのように書いてもエラーが出てきません。

どれもエラーですが、宣言をする・しないでエラーの内容が異なることが分かりました。

変数の宣言をしていない

宣言キーワードがないとき「is not defined」という状態になります。

console.log(a);  // エラー
a;

このときのエラーは
「Uncaught ReferenceError: a is not defined」
(参照エラー:aは定義されていません)

「is not defined = 未定義」と訳すとundefinedと混同してしまうので書き分けたいなと思ったのですが、
この時点では letなどの宣言キーワードを使うのか、function の関数宣言を使うのか、aが何者なのかもはっきりしていないので、そういう意味では「未定義」でいいのかなと思いました。

変数の宣言をしている

一方で宣言キーワードがある場合は、変数にアクセスできない、というエラーが出ます。

console.log(d);  // エラー
let d = 1;

このときのエラーは
「Uncaught ReferenceError: Cannot access 'd' before initialization」
(参照エラー:初期化前に変数dにアクセスできません)

「'd'」とクォーテーションで囲まれているあたりが、「変数」と宣言はされている扱いになっているように思いました。

よって変数にアクセス(呼び出し)前に宣言と初期化が必要なのかなと思いました。

後で変数(あるいは文字)を呼び出す

今度は先に変数宣言をしたり、初期値を代入したり、しなかったりしてみました。

変数を宣言していない、初期値を代入していない

謎の「e」を呼び出すのは確かにエラーがでると予想できます。
しかも他と違い、エラーの位置が変数(もどき)を記述した行にありました。

e;      // エラー
console.log(e);

エラーは
「Uncaught ReferenceError: e is not defined」
(参照エラー:eは定義されていません)

先に変数を呼び出したときのab もそうですが、代入もされていないただの文字では、出てきただけでエラーになるのは納得できました。

変数を宣言していない、初期値を代入している

宣言していないにも関わらず初期値が代入されているという状態です。

f = 1;    
console.log(f);  // 1

[変数] 変数の基本|業務ができる中級者になるためのJavaScript入門(文法編)

JavaScriptは明示的に宣言をしなくても、初めて値が代入されるタイミングで暗黙的に変数を用意してくれます。宣言なしで作成された変数はすべてグローバル変数となる ので、宣言の省略は避けるべきです。

値が代入されているので、上記でいう f は変数と判断されるのだろうと思います。
宣言キーワードがないとブロックスコープが効かずグローバル変数になってしまうので、宣言キーワードを付けて初期値を代入しましょうとういうことですね。

変数を宣言している、初期値を代入していない

undefinedが代入されます。

let g;    
console.log(g);  // undefined

上記と同じ記事に書かれていたletについて、

初期値を指定しないで宣言できますが、その場合はデフォルト値として、undefined(未定義) という値が割り当てられます。またconstと同じく、同じ変数名での宣言はできません。なお、letは何かの略ではなく、数学で利用される「仮に~とする」という英単語です。

宣言した変数には初期値 undefined が代入されているということですね。

変数を宣言している、初期値を代入している

この書き方が最も平和です。

let h = 1;    
console.log(h);  // 1

ちなみに const の場合は、宣言と初期値の代入を同時に行わないとエラーになります。
↓宣言のみの場合

const gg;      // エラー
console.log(gg);

「Uncaught SyntaxError: Missing initializer in const declaration」
(構文エラー:const宣言に初期値がありません)

というエラーが出ます。

ホイスティングを考える

ここでホイスティングについて調べました。

ホイスティング(宣言の巻き上げ)
コンテキスト内で宣言した変数や関数の定義をコード実行前にメモリに配置すること。

console.log(d);  // エラー
let d = 1;

↑こちらはホイスティングが起こり、コード実行より先に let d と変数dの宣言がされます。が、コードで代入部分が来るまでは初期値が代入されないためアクセスできず(呼び出せない)エラーになります。

let g;    
console.log(g);  // undefined

↑ホイスティングが起こっている状況はこのコードと似ていますが、この場合は undefined割り当られるのです。

この2つが混乱していました。

これでブロックスコープとホイスティングが絡んだ変数宣言の問題が解けそうです。

let のホイスティングは無い?

let - JavaScript | MDN

var と let の違いは、後者が宣言文に到達した後でしかアクセスできないという点があります(一時的なデッドゾーンを参照)。このため、let 宣言は一般に巻き上げされないと見なされています。

宣言だけ行われて、初期化されず(undefinedが代入されない)参照エラーになることから、ホイスティングではないということでしょうか。

一時的なデッドゾーン(TDZ)というのがあるのですね。
「letにはホイスティングがないという見方もある」と覚えておきます。。

関数の宣言

関数についても少し書きます。

function 宣言 - JavaScript | MDN

関数宣言は宣言より先に実行のコードがあっても、ホイスティングにより宣言部分が巻き上げられて、実行のコードで実行します。
関数式は巻き上げられないことだけ注意すれば、すごくシンプルです。

メモリと変数宣言、代入、参照あたりについて

変数に代入されている値がどのように保存されているのか、図を作って整理します。

こちらの記事を参考にさせていただいて図を書きました。

[変数] 変数とメモリ|業務ができる中級者になるためのJavaScript入門(文法編)

※この図のような挙動はブラウザなどによって異なるそうです。

図の一番下の abc = def; は、値をコピーして他の記憶領域を確保しているのだと思います。
参照先をコピーするのはオブジェクトの場合だと他で学んだので、参考記事はブラウザなどの環境による違いなのでしょうか。。(私が正しく解釈できていないだけかも)

変数とは別の記憶領域の番地を保管した記憶領域のことで、変数名とは記憶領域に設定されたエイリアス(別名)のようなものです。

変数を使うことで、同じ値でも違った用途がある複数の値を扱うことができるのですね。
変数名と変数に代入した値、それぞれ記憶領域があるというイメージは持っておきたいです。

変数に値を格納することを代入する(assignment) と言い、初めて行う代入は特に初期化(initialization) といいます。

(中略)

変数に何も代入しなくても未定義であることを示す値となるundefinedが自動で格納されます。つまり正確には空の変数というものは存在しません。

ここで let のホイスティングを思い出すと、変数宣言だけされて初期化されない状態は何なのだろうと思いました。

ホイスティングは「コンテキスト内で宣言した変数や関数の定義をコード実行前にメモリに配置すること。」とあるので、変数名だけメモリに配置されているのかと推測しましたが、。。どうなのでしょう、調べきれませんでした。

本当は関数の宣言や関数式について図が作れたら良かったのですが、またの機会にします。

さいごに

Udemyの動画でスコープやコンテキスト、クロージャーという言葉が出てくると苦手意識が出てくるようになったので、早めに克服したいです。他の言語でも使われる言葉(多分)ですし。。

おしまい。