はじめに
未知の仕様がありましたので共有です。
const f = () => console.log(x)
const x = 'xx'
f()
このコードを実行すると、const x ...つまり、xxと表示されます。
const fの定義時点ではxが何者か定義されていませんが、これでも動くんですね。
(これはこれで、エラーにしてもいいのでは・・・?)
では、これではどうでしょう?
const f = () => console.log(x)
- const x = 'xx'
f()
+ const x = 'xx'
const xがf()の実行時に定義されていません。
エラーが発生するでしょう。
ReferenceError: x is not defined(xが定義されてないよ)と表示されるはずです。
答え
ReferenceError: Cannot access 'x' before initialization
あれ?
予想は「xが定義されてないよ」でしたが、
実際は「xが初期化する前にアクセスすることはできないよ」でした。
試しに、const xを消してみましょう。
これこそ、ReferenceError: x is not defined(xが定義されてないよ)が表示されるはずです。
const f = () => console.log(x)
f()
- const x = 'xx'
ReferenceError: x is not defined
予想通りですね。
ではやはり、最初のエラーは「xが定義されていない」とは別のエラーのようです。
TDZの影響
letまたはconst変数は、ブロックの始まりからコードが実行されて変数が宣言され初期化される行に到達するまでは、「一時的なデッドゾーン」(TDZ) 内にあると言います。
人によっては let, const, class を巻き上げが行われないと見なしますが、それは一時的デッドゾーンで宣言前に変数を使用することを厳しく禁止しているからです。
なるほど。どうやら一時的デッドゾーン(TDZ)というものが存在するようです。
直接、件のコードに対する言及ではありませんが、以下の記述がありました。
const宣言が定義されているスコープ全体をまだ「汚染」しているため、console.log(x)文はまだ初期化されていないconst x = 2宣言からxを読み込み、ReferenceErrorを発生します。
という説明がありました。「汚染」いいですね。
- 初期化(宣言)の行われたスコープはTDZにより、ある種「汚染」される
- その中での呼出は
ReferenceError: Cannot access 'x' before initializationを発生させる - 事前に宣言されていても、TDZによるエラーが優先される
他の実例
これはどうでしょう?
f()とconsole.log(x)の2箇所でxの出力を試みています。
const f = () => {const x = 'xxx'; console.log(x) }
f()
console.log(x)
const x = 'xx'
一見、どちらもconst x = 'xx'の影響でエラーになりそうですが・・・
xxx
/home/runner/testtdz/tdz.js:3
console.log(x)
^
ReferenceError: Cannot access 'x' before initialization
xxxが出力されているので、最初のf()は予想に反して動いていますね。
console.log(x)は予想通りのエラーです。
なぜ、f()はエラーにならないのでしょう?
答え
スコープが違うから
f()は、関数内で宣言されたconst x = 'xxx'を優先します。関数内にxが存在する以上、これ以外のxを認識しません。
よって、TDZによる「汚染」は起きていないということですね。
なので、例えば以下の例ではReferenceError: Cannot access 'x' before initializationになります。
const f = () => { console.log(x); const x = 'xxx' }
f();
おわりに
知らない挙動がまだまだありますね。
さて、次は何を書こうかしら・・・