JavaScriptのExecution Context とは

JavaScript

Execution Contextとは

Twitterでも呟きましたが「Execution Context」はJavaScriptが評価・実行されるときの環境を抽象化したものを指すようです。とはいっても全くイメージつかないのでもう少し掘り下げて行きたいと思います。

Execution Contextには3つの種類がある

Execution Contextには以下の3つの種類が存在します。

  • Global Execution Context
  • Functional Execution Context
  • Eval Function Execution Context

Global Execution Context

Global Execution ContextはJavaScriptを実行した時にディフォルトで設定されるExecution Contextです。関数内以外のコードはこのGlobal Execution Contextに該当します。またJavaScriptを実行したとき、Global Execution Contextはプログラム中に1つしか存在しません。Global Execution Contextは次の役割を担います。

  • window objectの作成
  • thisをグローバルオブジェクトに等しくなるように設定

Functional Execution Context

Functional Execution Contextは各関数に対して設定されるExecution Contextです。Functional Execution Contextは関数が呼び出されるごとに生成されます。論理的な生成数の制限はありません。

Eval Function Execution Context

Eval Function Execution Contextはeval関数内で実行されるときに設定されるExecution Contextです。一般的にeval関数は使うことは推奨されていませんので今回の記事ではEval Function Execution Contextは扱わないことにします。

Execution Stackについて

生成されたExecution ContextはExecution Stack(実行スタック)に保存されます。実行スタックはLIFOと呼ばれる、最後に入れたものを最初に取り出すと呼ばれる構造を持っています。初めにJavaScriptを実行したとき、Global Execution Contextが初めに生成され、これを実行スタックに追加します。その後もJavaScriptのエンジンがFunctional Execution Contextを見つけるたびに、これを実行スタックに追加します。追加されたExecution Contextは一番上に積み上げられます。関数の処理が終了した場合、その関数が持つExecution Contextは実行スタックから削除されます。これらを理解するために以下のコードをみてください。

let a = 'hoge';
function first() {
  console.log('①Functional Execution Context');
  second();
  console.log('③Functional Execution Context');
}
function second() {
  console.log('②Functional Execution Context');
}
first();
console.log('④Global Execution Context');

// 出力
①Functional Execution Context
②Functional Execution Context
③Functional Execution Context
④Global Execution Context

Global Execution Context、first関数のFunctional Execution Context、second関数のFunctional Execution Contextの順番で実行スタックに積み上げられていきます。そして出力されているログやプログラムの順番を見てわかる通り、初めにsecond関数の処理が終了します。よってsecond関数のFunctional Execution Contextが実行スタックから削除され、次にfirst関数の処理が終了、first関数のFunctional Execution Contextも削除されます。全てのコードが実行されると、JavaScriptのエンジンがGlobal Execution Contextを削除するというフローになります。このようにしてLIFO的な構造が成り立っていることがわかるかと。

Execution Contextはどのようにして生成されるのか

Execution Contextが生成されるまで次の2つの段階があります。

  • LexicalEnvironmentが生成
  • VariableEnvironmentの生成

これら2つのEnvironmentを追っていきましょう。

LexicalEnvironmentについて

LexicalEnvironmentは変数・関数名とメモリに確保されている値との対応を持った構造のことを指します。次のコードをみてください。

var a = 1;
var b = 2;
function hoge() {
  console.log('hoge');
}

このコードのLexicalEnvironmentは次のようになります。

lexicalEnvironment = {
    a: 1,
    b: 2,
    foo: 
}

Environment Record

LexicalEnvironmentはEnvironment Recordという要素を持ちます。宣言された変数や関数はこのEnvironment Recordに保存されます。

外側の環境への参照を持つ

LexicalEnvironmentは外側の環境への参照を持ちます。JavaScriptのエンジンが現在見ているLexicalEnvironmentに参照したい変数が見つけられない場合、外側の環境に対して変数の参照を探しにいきます。

thisの定め方

LexicalEnvironmentにてthisの値は定められます。具体的に言うとthisが指す値は関数の呼び出し方によって変わります。オブジェクトの参照によって関数が呼び出された場合はthisはそのオブジェクトに設定されます。そうでない場合はグローバルオブジェクト、strict modeの場合はundefinedとして設定されます。これらを実際に確認するために次のコードを見てください。

const person = {
    name: 'katuo',
    birthYear: 1920,
    calcAge: function () {
        console.log(this)
        console.log(2020 - this.birthYear);
    }
  }
  // オブジェクト参照による呼び出し
  person.calcAge(); 
  // 出力
  // {name: "katuo", birthYear: 1920, calcAge: ƒ}
  // 100
  const calculateAge = person.calcAge;
 // 通常の呼び出し
  calculateAge();
  // 出力
  // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
  // NaN

thisの指している値がオブジェクトであるか、グローバルオブジェクトであるかの違いがわかるかと思います。また、GlobalExectionContextのLexicalEnvironmentとFunctionExectionContext のLexicalEnvironmentをまとめると次のようになります。

GlobalExectionContext = {
    LexicalEnvironment: {
      EnvironmentRecord: {
        Type: "Object",
      }
      outer: [null],
      this: [global object]
    }
  }
  FunctionExectionContext = {
    LexicalEnvironment: {
      EnvironmentRecord: {
        Type: "Declarative",
      }
      outer: [Global or outer function environment reference],
      this: [depends on how function is called]
    }
  }

Variable Environmentについて

VariableEnvironmentも LexicalEnvironmentと基本的には同じものですが、大きく異なる点としてLexicalEnvironmenはletとconst、VariableEnvironmentはvar変数のみを保持するという点です。この違いを理解するために次のコードを使って解説します。

let a = 1;
const b = 2;
var c;
function multiply(e, f) {
 var g = 10;
 return e * f * g;
}
c = multiply(1, 2);

このコードを実行された時、JavaScriptのエンジンはまずGlobal Execution Contextを生成します。creation phaseにおいてGlobalExectionContextは次のようになります。

GlobalExectionContext = {
    LexicalEnvironment: {
      EnvironmentRecord: {
        Type: "Object",
        a: [ uninitialized],
        b: [ uninitialized],
        multiply: [ func]
      }
      outer: [null],
      ThisBinding: [Global Object]
    },
    VariableEnvironment: {
      EnvironmentRecord: {
        Type: "Object",
        c: undefined,
      }
      outer: [null],
      ThisBinding: [Global Object]
    }
  }

var変数で定義されたcはVariableEnvironmentに定義されていることが確認できるかと。ちなみにlet、contで定義された変数には値が格納されていませんが、varで定義された変数はundefinedが格納されます。実はこれホイスティング の話にも繋がります。次のコードをみてください。

console.log(hoge_let) // Uncaught ReferenceError: Cannot access 'hoge_let' before initialization
console.log(hoge_const) // Uncaught ReferenceError: Cannot access 'hoge_const' before initialization
console.log(hoge_var) // undefined

let hoge_let = 1
const hoge_const = 2
var hoge_var = 3

letやconstで定義した変数を宣言前に呼び出そうとするとUncaught ReferenceErrorが発生します。これは初期化状態のときに呼び出すためエラーとなるのです。しかしvarで定義した変数は宣言前でも呼び出すことができます。そしてexecution phaseになると変数の割り当ては終了しますのでaとbに値が格納され次のようになります。

GlobalExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          a: 1,
          b: 2,
          multiply: [ func ]
        }
        outer: [null],
        ThisBinding: [Global Object]
      },
    VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          c: undefined,
        }
        outer: [null],
        ThisBinding: [Global Object]
    }
}

multiply関数が呼び出されると、Functional Execution Contextが生成されます。このときのFunctional Execution Contextは次のようになります。

FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
        Type: "Declarative"
        Arguments: {0: 1, 1: 2, length: 2},
        },
        outer: [GlobalLexicalEnvironment],
        ThisBinding: [Global Object or undefined],
    },
    VariableEnvironment: {
        EnvironmentRecord: {
        Type: "Declarative"
        g: undefined
        },
        outer: [GlobalLexicalEnvironment],
        ThisBinding: [Global Object or undefined]
    }
}

multiply関数の処理終了すると次のようになります。

FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
        Type: "Declarative"
        Arguments: {0: 1, 1: 2, length: 2},
        },
        outer: [GlobalLexicalEnvironment],
        ThisBinding: [Global Object or undefined],
    },
    VariableEnvironment: {
        EnvironmentRecord: {
        Type: "Declarative"
        g: 10
        },
        outer: [GlobalLexicalEnvironment],
        ThisBinding: [Global Object or undefined]
    }
}

VariableEnvironmentの変数gに値が格納されています。内部の様子は書いてはいませんが、GlobalExectionContextの変数cも計算結果の値で更新されます。

まとめ:Execution Contextは実行スタックのタスクのようなもの

話は巻き戻りますが、Execution Contextは1つのGlobal Execution Contextと無数のFunctional Execution Contextを持ち、これらがJavaScriptのエンジンによって実行スタックに追加・削除されることで処理が進むということがわかりました。かなり語弊がある表現かもしれませんが、Execution Contextは実行スタックのタスクのようなものと考えるとわかりやすいかもしれません。

今回参考にした記事です。非常にわかりやすかったです。

Understanding Execution Context and Execution Stack in Javascript
Understanding execution context and stack to become a better Javascript developer.

番外:おすすめアイテム

記事の内容とは別に自宅・オフィスでの開発がもっと快適に、楽しくなる商品を紹介します。アイテム1つで開発効率が一気に向上したり、開発のモチベーションが上がったりするので良いアイテムにはお金を投資すると良いかと。

KATUO
KATUO
今回紹介する商品は普段自分が使っているものなので自信を持ってお勧めできますー。では1つずつ紹介していきます。

おすすめアイテム①

自分はオフィス次の2点の座布団と背もたれクッションを利用してます。姿勢が安定し、お尻への負担が激減します。オフィスにいるのに自宅で作業しているような感覚になるので気に入ってます。また疲労感もかなり減るので費用対効果が非常に高いアイテムです。

おすすめアイテム②

ブックスタンドみなさん使ってますか?PCを打ちながら参考書見る時に手で抑える必要がなくなるので非常に便利です。自分も購入した時なんでいままで使ってなかたんだろうと後悔したのでまだ持ってない方は買っておくと作業効率があがります。

おすすめアイテム③

PCスタンド、magic keyboard、magic mouseを購入してから、開発効率があがり、身体的負担が減って快適に開発ができるようになりました。

エンジニアの基礎知識・教養

別の記事でエンジニアとして知っておくべき技術本についてまとめました。初心者には最低限読んでおいて欲しい技術本をまとめました。例えば「リーダブルコード」などは技術力の高いエンジニアなら必ず過去に読んでいる本だと思います。

筆者
筆者

今回の記事の内容は以上で終了です!最後まで読んでくれてありがとうございました!Twitterもやってるのでよかったらフォローしてください!

タイトルとURLをコピーしました