`Lexical Environment`(レキシカル環境)と`closure`(クロージャ)の関係を理解する
2023-02-19
課題感
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の仕様書に書いてあることでブラウザベンダーがどのように実装しているかは別の話とのこと
