`
rensanning
  • 浏览: 3514956 次
  • 性别: Icon_minigender_1
  • 来自: 大连
博客专栏
Efef1dba-f7dd-3931-8a61-8e1c76c3e39f
使用Titanium Mo...
浏览量:37500
Bbab2146-6e1d-3c50-acd6-c8bae29e307d
Cordova 3.x入门...
浏览量:604395
C08766e7-8a33-3f9b-9155-654af05c3484
常用Java开源Libra...
浏览量:678169
77063fb3-0ee7-3bfa-9c72-2a0234ebf83e
搭建 CentOS 6 服...
浏览量:87329
E40e5e76-1f3b-398e-b6a6-dc9cfbb38156
Spring Boot 入...
浏览量:399865
Abe39461-b089-344f-99fa-cdfbddea0e18
基于Spring Secu...
浏览量:69086
66a41a70-fdf0-3dc9-aa31-19b7e8b24672
MQTT入门
浏览量:90519
社区版块
存档分类
最新评论

【转】图解JavaScript的作用域链(Scope Chain)

 
阅读更多

用語の定義

・本エントリの文章における表記は、以下の表の「ECMA-262 3rd」に統一する
・本エントリの図における表記は、以下の表の「本エントリの略称」に統一する
・本エントリ内におけるES3とは、Standard ECMA-262 3rd editionを指す

ECMA-262 3rd 本エントリの略称 JavaScript(サイ本)第5版(日本語)
Execution Contexts EC 実行コンテキスト
Variable Object VO 変数定義のために使われるオブジェクト
Activation Object AO Callオブジェクト
Global Object GO グローバルオブジェクト
Function Object FO Functionオブジェクト
Scope Chain SC スコープチェーン
[[Scope]] [[Scope]] 言及なし?

※表中の「JavaScript(サイ本)第5版(日本語版)」は比較のための参考情報

前置き

Execution Contextとは?

JavaScriptのスコープチェーンを理解するためには、まずExecution Contextについて理解する必要がある。Execution Contextとは「論理的にスタックの構造をしたもの」であり、コントロールが実行可能コード(Global Code、Eval Code、Function Codeの3つ)に移ると新しいExecution Contextに入る(ES3 10)。つまり、以下の図のように、コントロールが実行可能コードに移ると新しいExecution Contextがスタックにpushされ、そのコードから戻るとスタックからpopされると捉えることができる。

f:id:maeharin:20130310152650j:image

Execution Contextに入ると何が起きるか?

では、新しいExecution Contextに入ると何が起きるのだろうか?ES3では以下3つの処理が行われると定義されている(ES3 10.1.6)。

(1) Scope Chainの生成・初期化
(2) Variable Instantiationの実行
(3) thisの値の決定

上記それぞれにおいて、具体的にはどのような処理が行われるのだろうか?これは実行可能コードの種類によって異なるとされている。そして実行可能コードには3つの種類のコードが存在し、それぞれ以下の通りである(ES3 10.2)。なお、本エントリでは、このうち(1) Global Codeと(3) Function Codeについて扱う。

(1) Global Code
(2) Eval Code
(3) Function Code

サンプルコード

本エントリでは、以下のサンプルコードについて図解を用いて紐解いていくことにする。

var x = 'xxx';                                                                                                                                                                                              

function foo () {
  var y = 'yyy';

  function bar () {
    var z = 'zzz';
    console.log(x + y + z); //xxxyyyzzz
  }

  bar();
}

foo();

 

最初にGlobal Objectが存在する

まず、いかなるExecution Contextに入るよりも前に、唯一のGlobal Objectが生成される(ES3 10.1.5)。初期状態のGlobal ObjectにはMathやStringといったBuilt-in objectsとブラウザ環境におけるwindowオブジェクトのようなホストが定義するプロパティがセットされる(この点について今回は割愛する)。

f:id:maeharin:20130312082123j:image

Global Code

 

◆ 新しいExecution Contextに入る

Global Objectが生成された後、Global Codeにコントロールが移る。すると、新しいExecution Contextがスタックに積まれる。そして冒頭記載したようにこのExecution Contextを元に(1) Scope Chainの生成・初期化 (2) Variable Instantiationの実行 (3) thisの値の決定が行われる。詳細は後述するが、これらの処理はExecution Contextに属するScope Chain(図中SC)、Variable Object(図中VO)、thisの値を決定するものと捉えることができる。

f:id:maeharin:20130310152651j:image

◆ Scope Chainの生成・初期化

新しいExecution Contextに入ると、まずScope Chainの生成・初期化が行われる。では、Scope Chainとは何か?ES3によるとScope Chainとは識別子の検索に使われるオブジェクトの「リスト」のことであり、そのExecution Contextの実行中はwith文かcatchを使わない限り不変である(ES3 10.1.4)。なお「リスト」という概念をどう実装するかはES3では定義されていない。そのため、ここでは純粋な「配列」であると考える。では、Global CodeにおけるScope Chainの生成・初期化はどのようになるのか?これはいたってシンプルで、Global CodeにおけるScope ChainはGlobal Objectのみを含むリストとなる(ES3 10.2.1)。

f:id:maeharin:20130310152652j:image

◆ Variable Instantiationの実行

Scope Chainの生成・初期化が行われると、次にVariable Instantiationが行われる。Variable InstantiationとはVariable Objectという特殊なオブジェクトにプロパティとその値を追加する処理のことである。全てのExecution ContextにはVariable Objectという特殊なオブジェクトが存在し、そのExecution Contextのコードにおける変数と関数の宣言はそのVariable Objectのプロパティとして追加されることになる。なお、どのオブジェクトがVariable Objectになるかはコードの種類によって異なり、Global Codeの場合はGlobal ObjectがVariable Objectとなる(ES3 10.1.3, 10.2.1)。

f:id:maeharin:20130310152653j:image

そしてVariable Instantiationでは以下の順番でVariable Objectにプロパティとその値が追加されていく(ES3 10.1.3)

(1) (Function Codeの場合)仮引数がVariable Objectのプロパティとして追加され、その値として実引数がセットされる。
(2) 対象コード内のFunctionDeclaration(関数宣言。関数式は除く)全てに対して、その宣言における関数名がVariable Objectのプロパティとして追加され、その値として新たに生成されたFunction Objectがセットされる。
(3) 対象コード内のVariableDeclaration(変数宣言)全てに対して、その宣言における変数名がVariable Objectのプロパティとして追加され、その値としてundefinedがセットされる。
※ 必ず1→2→3の順番で実行される

今回のサンプルコードにおいて、Global Codeでは変数xの宣言(VariableDeclaration)と、関数fooの宣言(FunctionDeclaration。仮引数はなし)が行われている。上記のルールに従うと、まず関数fooの宣言が処理され(今回はFunction CodeではなくGlobal Codeのため(1)の仮引数の処理は行われない)、その後変数xの宣言が処理されることになる。では、まず関数fooの宣言の方はどのように処理されるのだろうか?

FunctionDeclarationにおいては、前述したようにその宣言における関数名(ここではfoo)がVariable Object(ここではGlobal Object)のプロパティとして追加され、その値として新たに生成されたFunction Objectがセットされる。新しく生成されたFunction Objectは[[Scope]]プロパティという内部的なプロパティを持ち、その値として現在のExecution ContextのScope Chainが参照しているオブジェクトと同じオブジェクト(ここではGlobal Objectのみ)を参照するリストがセットされる(ES3 13.2)。

f:id:maeharin:20130310152654j:image

これでFunctionDeclarationの処理が済んだので、次にVariable Declarationが処理される。VariableDeclarationにおいては、前述したようにその宣言における変数名がVariable Objectのプロパティとして追加され、その値としてundefinedがセットされる。つまり、今回の例ではxがGlobal Objectのプロパティに追加され、その値としてundefinedがセットされる(この段階ではまだ値が'xxx'にならない)。

f:id:maeharin:20130310152655j:image

◆ thisの値の決定

上記のようにしてVariable Instantiationの実行が済むと、次はthisの値が決定される。thisとは全てのアクティブなExecution Contextに関連づいており、どのような値になるかは呼び出し元のオブジェクトと実行されるコードの種類によって異なり、その値は一度設定された後は不変である(ES3 10.1.7)。そしてGlobal Codeにおいてthisの値は常にGlobal Objectとなる(ES3 10.2.1)。そのためこの時点でthisの値はGlobal Objectとなる。

f:id:maeharin:20130310152656j:image

◆ コードを実行

ここまで来てはじめてコードの実行が行われる。今回のサンプルコードにおいては、プロパティxへの文字列'xxx'の代入。およびfoo()が実行されることになる。ここでxに'xxx'を代入する際には、識別子の探索(ES3 10.1.4)が行われる。具体的には現在のExecution ContextのScope Chainが参照しているVariable Objectを先頭から(図では0から)検索していき、識別子に該当するプロパティが存在したらそこで解決する。今回のサンプルではGlobal CodeのExecution ContextにおけるScope Chainが参照しているのはGlobal Objectのみであり、そこにプロパティxが存在するので、そこへ代入が行われる。

f:id:maeharin:20130310152657j:image

foo() - Function Code

◆ 新しいExecution Contextに入る

Global Codeにおいてfoo()が実行されると、新しいExecution Contextに入る。新しいfoo()のExecution Contextに入ると、Global Codeの時と同じようにScope Chainの生成・初期化やVariable Instantiationが行われる。ただし、Global Codeと異なり今回実行されるコードはFunction Codeである。「Scope Chainの生成・初期化、Variable Instantiation、thisの値の決定」はGlobal Codeのルールではなく、Function Codeのルールが適用される。

f:id:maeharin:20130310152658j:image

◆ Scope Chainの生成・初期化

Function CodeにおけるScope Chainの生成・初期化では、まずScope Chainの先頭にActivation Objectという特殊なオブジェクトがセットされる。なお、Activation Objectとは、Execution CodeがFunction Codeに入った時にVariable Objectとして使われるオブジェクトのことである。Activation Objectはまず関数の引数を表すargumentsプロパティの初期化を行う(argumentsに関する詳細については別の機会に言及する)その後Activation ObjectをVariable ObjectとしてVariable Instantiationが行われる。なお、Activation Objectとは仕様上の概念であり、プログラムからActivation Object自体には直接アクセスできない(Activation Objectのプロパティにはアクセスできる)(ES3 10.1.6)。

f:id:maeharin:20130310152659j:image

その後、呼び出した関数の[[Scope]]プロパティが参照しているオブジェクトがScope Chainにpushされる(ES3 10.2.3)。従って、今回のサンプルコードにおいて、foo()を実行した直後におけるExecution ContextのScope ChainはActivation Object(foo()の実行によって作られたもの。図ではAO_1とする)とGlobal Objectを参照していることになる。なお、ここでのポイントは、あくまでも呼び出した関数の[[Scope]]プロパティが使われるのであって、決してGlobal CodeのExecution ContextにおけるScopt Chainが使われる訳では無いということだ。このことは、より複雑な例になってきた際に重要となる(次回以降に扱う)

f:id:maeharin:20130310152700j:image

◆ Variable Instantiationの実行

先ほど触れたように、Function Codeでは、Activation ObjectをVariable ObjectとしてVariable Instantiationが行われる(ES3 10.2.3)。その点を除いては、Global Codeの時と同じ処理が行われるので、ここでのVariable Instantiation以下のような処理になる。

Function Declarationで宣言された関数をVariable Object(AO_1)にセットする(プロパティはbar、値は新たに生成されたFunction Object。そのFunction Objectの[[Scope]]プロパティの値は、AO_1とGlobal Objectを参照するリスト)

f:id:maeharin:20130310152701j:image

Variable Declarationで宣言された変数をVariable Object(AO_1)にセットする(プロパティはy、値はundefined)
f:id:maeharin:20130310152702j:image

◆ thisの値の決定

Function codeにおいて、thisの値はその呼び出し元オブジェクトから提供される(callやapplyメソッドでthisの値を指定できる)。もし呼び出し元オブジェクトから提供されたthisの値がオブジェクトでなければ(nullの場合も含む)、thisの値はGlobal Objectとなる(ES3 10.2.3)。今回のサンプルコードにおいては特にthisの値は指定されていないので、Global Objectがthisの値となる。

f:id:maeharin:20130310152703j:image

◆ コードを実行

ここでfooコードの実行が行われる。今回のサンプルコードにおいては、プロパティyへの文字列'yyy'の代入。およびbar()が実行されることになる。

f:id:maeharin:20130310152704j:image

bar() - Function Code

◆ 新しいExecution Contextに入る

bar()が実行されると、foo()の時と同じように新しいExecution Contextに入りScope Chainの生成・初期化等が行われる。

f:id:maeharin:20130310152705j:image

◆ Scope Chainの生成・初期化
foo()の時と同じようにScope Chainが生成・初期化される。まず新しいActivation Object(図ではAO_2)がScope Chainの先頭にセットされる。

f:id:maeharin:20130310152706j:image

その後、呼び出した関数の[[Scope]]プロパティが参照しているオブジェクトがScope Chainにpushされる。この段階でbar()のExecution ContextのScope Chainは、先頭からAO_2, AO_1, Global Objectということになる。

f:id:maeharin:20130310152707j:image

◆ Variable Instantiationの実行
Variable Declarationで宣言された変数zがAO_2のプロパティに追加される。そしてその値はundefinedである。

f:id:maeharin:20130310152708j:image

◆ thisの値の決定

foo()の時と同じく、ここで特にthisの値は指定されていないので、thisの値はGlobal Objectとなる。

f:id:maeharin:20130310152709j:image

◆ コードを実行

ここでbarコードが実行され、プロパティzに文字列'zzz'が代入される。

f:id:maeharin:20130310152710j:image

この時点で現在実行中のExecution Context(barのExecution Context)は、以下の図のような構成になっている。

f:id:maeharin:20130310152711j:image

この段階におけるconsole.log(x + y + z);というコードにおいて、識別子x, y, zはそれぞれ以下のように解決されるため、結果はxxxyyyzzzとなる。

x: AO_2にxを探すが見つからない→AO_1にxを探すが見つからない→GOにxが見つかり、その値は'xxx'
y: AO_2にyを探すが見つからない→AO_2にyが見つかり、その値は'yyy'
z: AO_2にzが見つかり、その値は'zzz'

 

原文地址:http://d.hatena.ne.jp/maeharin/20130313/javascript_scopechain

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics