まず第一に、実行環境とスコープは 2 つのまったく異なる概念であることを知っておく必要があります。
関数へのすべての呼び出しには、それに密接に関連するスコープと実行環境があります。基本的に、スコープは関数に基づいていますが、実行環境はオブジェクトに基づいています (たとえば、グローバル実行環境はウィンドウ オブジェクトです)。
言い換えれば、スコープには呼び出される関数の変数アクセスが含まれており、呼び出しシナリオが異なれば異なります。実行環境は常に this キーワードの値であり、現在実行中のコードを所有するオブジェクトへの参照です。各実行環境には変数オブジェクトが関連付けられており、環境内で定義されたすべての変数と関数はこのオブジェクトに格納されます。私たちが作成するコードはこのオブジェクトにアクセスできませんが、パーサーはデータを処理するときにバックグラウンドでこのオブジェクトを使用します。
実行環境 (実行コンテキスト – 実行コンテキストとも呼ばれます)
JavaScript インタープリターが実行コードを初期化すると、デフォルトで最初にグローバル実行環境に入ります。この時点から、関数を呼び出すたびに新しい実行環境が作成されます。 。
各関数には独自の実行環境があります。実行フローが関数に入ると、関数の環境が環境スタック (実行スタック) にプッシュされます。関数が実行されると、スタックはその環境をポップし、制御を前の実行環境に戻します。 ECMAScript プログラムの実行フローは、この便利なメカニズムによって制御されます。
実行環境は、作成と実行の 2 つの段階に分けることができます。作成フェーズでは、パーサーはまず、実行環境で定義された変数、関数宣言、パラメーターで構成される変数オブジェクト (アクティベーション オブジェクトとも呼ばれます) を作成します。この段階で、スコープ チェーンが初期化され、この値が最終的に決定されます。実行フェーズでは、コードが解釈されて実行されます。
デモ:
<script type="text/javascript"> function Fn1(){ function Fn2(){ alert(document.body.tagName);//BODY //other code... } Fn2(); } Fn1(); //code here </script>
概要
JavaScript コードがブラウザーにロードされると、デフォルトで最初にグローバル実行環境に入ります。グローバル実行環境で関数が呼び出されて実行されると、プログラム フローは呼び出された関数に入ります。このとき、JS エンジンは関数の新しい実行環境を作成し、それを実行環境スタックの最上位にプッシュします。ブラウザは常にスタックの最上位にある実行環境を実行します。実行が完了すると、実行環境はスタックの最上位からポップされ、その下の実行環境に入ってコードを実行します。このようにして、スタック内の実行環境は順次実行され、グローバル実行環境に戻るまでスタックからポップされます。
さらに、いくつかの点に注意してください:
シングルスレッド
同期実行
唯一のグローバル実行環境
ローカル実行環境の数に制限はありません
関数が呼び出されるたびに、独自の関数が複数回呼び出された場合でも、ローカル実行環境が作成されます (つまり、関数が複数回呼び出された場合、複数の異なるローカル実行環境が作成されます)。
スコープ
環境内でコードが実行されると、変数オブジェクトのスコープ チェーンが作成されます。スコープ チェーンの目的は、実行環境がアクセスできるすべての変数と関数への順序付けされたアクセスを保証することです。
スコープチェーンには、実行環境スタック内の各実行環境に対応する変数オブジェクトが含まれます。スコープ チェーンを通じて、変数へのアクセスと識別子の解決を決定できます。
注: グローバル実行環境の変数オブジェクトは、常にスコープ チェーンの最後のオブジェクトです。
変数にアクセスするときは、可視性の問題がなければなりません (内部環境は外部層の変数と関数にアクセスできますが、外部環境は内部層の変数と関数にアクセスできません)。さらに詳しく説明すると、変数にアクセスするとき、または関数を呼び出すとき、JavaScript エンジンはルールに従って、さまざまな実行環境の変数オブジェクトからリンク リストを構築します。変数にアクセスするときは、最初にリンク リスト内の最初の変数オブジェクトを検索します。見つからない場合は、グローバル実行環境の変数オブジェクト、つまりウィンドウ オブジェクトが見つかるまで 2 番目の変数オブジェクトの検索を続けます。これはスコープチェーンの概念も形成します。
スコープチェーン図は、実行環境とスコープの関係(1対1対応)、スコープ間の関係(連結リスト構造、上下関係)をわかりやすく表現します。
Demo:
var color = "blue"; function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; // 这里可以访问color, anotherColor, 和 tempColor } // 这里可以访问color 和 anotherColor,但是不能访问 tempColor swapColors(); } changeColor(); // 这里只能访问color console.log("Color is now " + color);
上記のコードには、グローバル実行環境、changeColor() のローカル実行環境、swapColors() のローカル実行環境の合計 3 つの実行環境が含まれています。
グローバル環境には変数 color と関数changecolor()があります。
changecolor()関数のローカル環境にはanothercolor属性とswapcolors関数があります。もちろん、changecolor関数はそれ自体とその周囲にアクセスできます。 、グローバル環境)
swapcolor() 関数の変数には、ローカル環境の変数 tempcolor があります。上記 2 つの環境 (changecolor と window) のすべての変数は、この関数内でアクセスできます。これらの 2 つの環境は親実行環境であるためです。
上記のコードのスコープチェーンは以下のようになります:
从上图发现。内部环境可以通过作用域链访问所有的外部环境,但是外部环境不能访问内部环境中的任何变量和函数。
标识符解析(变量名或函数名搜索)是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后(全局执行环境)回溯,直到找到标识符为止。
执行环境与作用域的区别与联系
执行环境为全局执行环境和局部执行环境,局部执行环境是函数执行过程中创建的。
作用域链是基于执行环境的变量对象的,由所有执行环境的变量对象(对于函数而言是活动对象,因为在函数执行环境中,变量对象是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO(变量对象)的角色。)共同组成。
当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途:是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。
小练习
<script type="text/javascript"> (function(){ a= 5; console.log(window.a);//undefined var a = 1;//这里会发生变量声明提升 console.log(a);//1 })(); </script>
window.a之所以是undefined,是因为var a = 1;发生了变量声明提升。相当于如下代码:
<script type="text/javascript"> (function(){ var a;//a是局部变量 a = 5;//这里局部环境中有a,就不会找全局中的 console.log(window.a);//undefined a = 1;//这里会发生变量声明提升 console.log(a);//1 })(); </script>