はじめに
未知の仕様がありましたので共有です。
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();
おわりに
知らない挙動がまだまだありますね。
さて、次は何を書こうかしら・・・