3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

`Lexical Environment`(レキシカル環境)と`closure`(クロージャ)の関係を理解する

Posted at

課題感

  • 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は以下のようになる

2023-02-19-01.png

コードブロック

  • コードブロックに処理が移ると新しいLexical Environmentが作成される
  • outerEnvというプロパティに外部(コードブロック前)のLexical Environmentを指し示す
  • グローバルスコープLexical EnvironmentouterEnvnullになる
  • outerEnvは値の保持ではなく、アドレスを保持(スナップショットではない)
let apple = '🍎'
{
    let banan = '🍌'
}

このコードのLexical Environmentは以下のようになる

2023-02-19-02.png

関数

  • 関数オブジェクトがLexical Environmentのプロパティに定義される
  • 関数が呼び出されると関数に対応した新しいLexical Environmentが生成される
  • outerEnvには、関数呼び出し元の参照がセットされる
let apple = '🍎'
const sayFruit = (fruit) => {
    console.log(fruit)
    console.log(apple)
}
sayFruit('🍌');

このコードのLexical Environmentは以下のようになる

2023-02-19-03.png

  • 変数にアクセスした場合、最初に自身のLexical Environmentを探し、見つからなければouterEnvを辿っていく
  • outerEnvnullになるまで辿り、見つからなければReferenceErrorが発生する
  • sayFruit関数内でappleにアクセスする時、自身のLexical Environmentにはappleがないので、outerEnvを辿り、グローバルのLexical Environmentappleが見つかる

関数を返す関数

  • 関数オブジェクトは[[environment]]プロパティを持っている
  • [[environment]]プロパティは、その関数オブジェクトが作られた場所のLexical Environmentを指し示す。※重要
const sayFruitFactory = () => {
    let sayCount = 0
    return (fruit) => {
        sayCount++
        console.log(fruit + sayCount)
    }
} 
const sayFruit = sayFruitFactory()
sayFruit('🍇')

このコードのLexical Environmentは以下のようになる

2023-02-19-04.png

  • sayFruit関数はsayFruitFactory関数の中で作られた関数なので、[[environment]]プロパティにはsayFruitFactory関数のLexical Environmentがセットされている
  • sayFruit関数が呼び出されると、その外部Lexical Environmentの参照は[[environment]]から取得される

冒頭のコードの解説

  • これまでの説明を踏まえて、冒頭のコードの動作を解説する
  • sayFruit1関数の[[environment]]プロパティにはグローバルのLexical Environmentがセットされている
  • そのため、sayFruit1関数内でfoodにアクセスすると、sayFruit1関数のLexical Environmentにはfoodがないので、outerEnv -> [[environment]]と辿り、グローバルのLexical Environmentfoodを見つけることになる
  • sayFruit1関数の直前に定義したfood🍌にはアクセスしない
    let food = '🍎'
    const sayFruit1 = () => console.log(food)

    const sayFruit2 = () => {
        let food = '🍌'
        sayFruit1() // disp 🍎. not 🍌
    }
    sayFruit2()

Lexical Environmentは以下のようになる

2023-02-19-05.png

まとめ

  • 変数はLexical Environmentのプロパティとして管理される
  • 関数呼び出しの度にLexical Environmentが作られる
  • outerEnvには、関数呼び出し元の参照がセットされる
  • 自身のLexical Environmentに変数がなければ、outerEnvを辿っていく
  • 関数オブジェクトは[[environment]]プロパティを持っている
  • [[environment]]プロパティは、その関数オブジェクトが作られた場所のLexical Environmentを指し示す
  • これはEcmaScriptの仕様書に書いてあることでブラウザベンダーがどのように実装しているかは別の話とのこと

参考

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?