課題感
-
closure
の理解があやふや -
closure
は、外側の変数の情報を持った関数と言われるが、下記のコードのsayFruit2
の中でsayFruit1
を呼び出した時、🍌でなく🍎が出力されことを感覚では理解していたが、どのような仕様のもとに動作しているのかを知らない
let food = '🍎'
const sayFruit1 = () => console.log(food)
const sayFruit2 = () => {
let food = '🍌'
sayFruit1() // disp 🍎. not 🍌
}
sayFruit2()
解説
-
Lexical Environment
という概念の理解が必要 - JavaScriptのグローバルスコープ、ブロックスコープ、関数には
Lexical Environment
と呼ばれるオブジェクトを保持している
変数
- 変数は
Lexical Environment
のプロパティとして管理される
let apple = '🍎'
このコードのLexical Environment
は以下のようになる
コードブロック
- コードブロックに処理が移ると新しい
Lexical Environment
が作成される -
outerEnv
というプロパティに外部(コードブロック前)のLexical Environment
を指し示す - グローバルスコープ
Lexical Environment
のouterEnv
はnull
になる -
outerEnv
は値の保持ではなく、アドレスを保持(スナップショットではない)
let apple = '🍎'
{
let banan = '🍌'
}
このコードのLexical Environment
は以下のようになる
関数
- 関数オブジェクトが
Lexical Environment
のプロパティに定義される - 関数が呼び出されると関数に対応した新しい
Lexical Environment
が生成される -
outerEnv
には、関数呼び出し元の参照がセットされる
let apple = '🍎'
const sayFruit = (fruit) => {
console.log(fruit)
console.log(apple)
}
sayFruit('🍌');
このコードのLexical Environment
は以下のようになる
- 変数にアクセスした場合、最初に自身の
Lexical Environment
を探し、見つからなければouterEnv
を辿っていく -
outerEnv
がnull
になるまで辿り、見つからなければReferenceError
が発生する -
sayFruit
関数内でapple
にアクセスする時、自身のLexical Environment
にはapple
がないので、outerEnv
を辿り、グローバルのLexical Environment
でapple
が見つかる
関数を返す関数
- 関数オブジェクトは
[[environment]]
プロパティを持っている -
[[environment]]
プロパティは、その関数オブジェクトが作られた場所のLexical Environment
を指し示す。※重要
const sayFruitFactory = () => {
let sayCount = 0
return (fruit) => {
sayCount++
console.log(fruit + sayCount)
}
}
const sayFruit = sayFruitFactory()
sayFruit('🍇')
このコードのLexical Environment
は以下のようになる
-
sayFruit
関数はsayFruitFactory
関数の中で作られた関数なので、[[environment]]
プロパティにはsayFruitFactory
関数のLexical Environment
がセットされている -
sayFruit
関数が呼び出されると、その外部Lexical Environment
の参照は[[environment]]
から取得される
冒頭のコードの解説
- これまでの説明を踏まえて、冒頭のコードの動作を解説する
-
sayFruit1
関数の[[environment]]
プロパティにはグローバルのLexical Environment
がセットされている - そのため、
sayFruit1
関数内でfood
にアクセスすると、sayFruit1
関数のLexical Environment
にはfood
がないので、outerEnv
->[[environment]]
と辿り、グローバルのLexical Environment
でfood
を見つけることになる -
sayFruit1
関数の直前に定義したfood
の🍌
にはアクセスしない
let food = '🍎'
const sayFruit1 = () => console.log(food)
const sayFruit2 = () => {
let food = '🍌'
sayFruit1() // disp 🍎. not 🍌
}
sayFruit2()
Lexical Environment
は以下のようになる
まとめ
- 変数は
Lexical Environment
のプロパティとして管理される - 関数呼び出しの度に
Lexical Environment
が作られる -
outerEnv
には、関数呼び出し元の参照がセットされる - 自身の
Lexical Environment
に変数がなければ、outerEnv
を辿っていく - 関数オブジェクトは
[[environment]]
プロパティを持っている -
[[environment]]
プロパティは、その関数オブジェクトが作られた場所のLexical Environment
を指し示す - これはEcmaScriptの仕様書に書いてあることでブラウザベンダーがどのように実装しているかは別の話とのこと