課題感
-
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の仕様書に書いてあることでブラウザベンダーがどのように実装しているかは別の話とのこと




