1. 古典的なケース 1
2. 古典的なケース 2
3. 古典的なケース 3
4. 古典的なケース 4
;
疑问:上面的代码又会输出什么呢?(小子,看这回整不死你!哇哈哈,就不给你选项)
答案:在FireBug中的运行结果是 undefined, 2,下面简单说一下具体执行过程
第一个alert输出undefined
第二个alert输出 2
思考:到底怎么回事儿?
5、经典案例五…………..N 看到上面的几个例子,你可能会想,怎么可能,我写了几年的 js 了,怎么这么简单例子也会犹豫,结果可能还答错了。其实可能原因是:我们能很快的写出一个方法,但到底方法内部是怎么执行的呢?执行的细节又是怎么样的呢?你可能没有进行过深入的学习和了解。要了解这些细节,那就需要了解 JS 引擎的工作方式,所以下面我们就把 JS 引擎对一个方法的解析过程进行一个稍微深入一些的介绍
解析过程
1、执行顺序
- 编译型语言,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成。
- 解释型语言,通过词法分析和语法分析得到语法分析树后,就可以开始解释执行了。这里是一个简单原始的关于解析过程的原理,仅作为参考,详细的解析过程(各种JS引擎还有不同)还需要更深一步的研究
JavaScript执行过程,如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),它们的运行顺序是:
- 步骤1. 读入第一个代码段(js执行引擎并非一行一行地执行程序,而是一段一段地分析执行的)
- 步骤2. 做词法分析和语法分析,有错则报语法错误(比如括号不匹配等),并跳转到步骤5
- 步骤3. 对【var】变量和【function】定义做“预解析“(永远不会报错的,因为只解析正确的声明)
- 步骤4. 执行代码段,有错则报错(比如变量未定义)
- 步骤5. 如果还有下一个代码段,则读入下一个代码段,重复步骤2
- 步骤6. 结束
2、特殊说明
全局域(window)域下所有JS代码可以被看成是一个“匿名方法“,它会被自动执行,而此“匿名方法“内的其它方法则是在被显示调用的时候才被执行
3、关键步骤
上面的过程,我们主要是分成两个阶段
- 解析:就是通过语法分析和预解析构造合法的语法分析树。
- 执行:执行具体的某个function,JS引擎在执行每个函数实例时,都会创建一个执行环境(ExecutionContext)和活动对象(activeObject)(它们属于宿主对象,与函数实例的生命周期保持一致)
3、关键概念
到这里,我们再更强调以下一些概念,这些概念都会在下面用一个一个的实体来表示,便于大家理解
- 语法分析树(SyntaxTree)可以直观地表示出这段代码的相关信息,具体的实现就是JS引擎创建了一些表,用来记录每个方法内的变量集(variables),方法集(functions)和作用域(scope)等
- 执行环境(ExecutionContext)可理解为一个记录当前执行的方法【外部描述信息】的对象,记录所执行方法的类型,名称,参数和活动对象(activeObject)
- 活动对象(activeObject)可理解为一个记录当前执行的方法【内部执行信息】的对象,记录内部变量集(variables)、内嵌函数集(functions)、实参(arguments)、作用域链(scopeChain)等执行所需信息,其中内部变量集(variables)、内嵌函数集(functions)是直接从第一步建立的语法分析树复制过来的
- 词法作用域:变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,通过静态分析就能确定,因此词法作用域也叫做静态作用域。 with和eval除外,所以只能说JS的作用域机制非常接近词法作用域(Lexical scope)
- 作用域链:词法作用域的实现机制就是作用域链(scopeChain)。作用域链是一套按名称查找(Name Lookup)的机制,首先在当前执行环境的 ActiveObject 中寻找,没找到,则顺着作用域链到父 ActiveObject 中寻找,一直找到全局调用对象(Global Object)
4、实体表示
解析シミュレーション
これを見ても、誰もがまだ混乱していると思います。解析ツリーとは何なのか、解析ツリーはどのようなものなのか、スコープ チェーンはどのように実装されているのか、アクティブ オブジェクトの内容は何なのかなど。あまり明確ではありません。以下では、実際のコードを使用して解析プロセス全体をシミュレーションします。実際に構文解析ツリーとアクティブ オブジェクトを作成し、スコープとスコープ チェーンの実装方法を理解します。
1シミュレーション コード
/* グローバル ( window) ドメイン コードの一部*/
var i = 1,j = 2,k = 3;
function a(o,p,x,q){
var x = 4; >alert(i );
function b(r,s) {
var i = 11,y = 5;
function c(t){
var z = 6;
alert(i);
//関数式
var d = function(){
alert(y); c(60);
b(40,50);
; 🎜>2 , 解析ツリー
上記のコードは非常に単純です。最初にいくつかのグローバル変数とグローバル メソッドを定義し、次にメソッド内でローカル変数とローカル メソッドを定義します。ここで、JS インタープリターがこのコードを読み取り、解析を開始します。前述したように、JS エンジンはまず構文解析と事前解析を通じて構文解析ツリーを取得します。構文解析ツリーがどのようなもので、どのような情報が含まれているかについては、以下では単純な構造である JS オブジェクトを使用します (わかりやすくするため)。さまざまなオブジェクトを表します (ここにあるものは単なる擬似オブジェクト表現であり、実行できない場合があります) 構文解析ツリーを記述します (これは私たちがよく知っているものです。実際の構造については掘り下げません。実際の構造はさらに複雑になるはずです)これは、特に解析プロセスを理解するのに役立ちます。
コードをコピーします。
コードは次のとおりです。 >
/**
* 構文解析ツリーの構築をシミュレートし、変数とメソッドを関数に格納します
*/
}、
関数:{
a: this.a
}
}、
a:{
変数:{
x:"未定義"
}、
関数:{
b: this.b
},
スコープ: this.window
} ,
b:{
変数:{
y:"未定義"
},
関数:{
c: this.c、d: this .d
}、
スコープ: this.a
}、
c:{
変数:{
z:"未定義"
}、
関数:{}、
スコープ: this.b
}、
d:{
変数:{}、
関数:{},
スコープ: {
myname:d,
スコープ: this.b
}
}
}; >上記は解析ツリーを簡単に表現したものです 先ほど分析したように、解析ツリーは主に各関数の変数セット(変数)、メソッドセット(関数)、スコープ(スコープ)を記録します
解析ツリーのキーポイント解析ツリー
1 変数セット (変数)、変数定義のみがあり、変数値はありません。このとき、変数値はすべて「未定義」です。
2 スコープ (スコープ)。レキシカルスコープの特性により、各変数のスコープはこの時点ですでに明確であり、実行環境によって変更されることはありません。 【それはどういう意味ですか?つまり、メソッドを返し、それを別のメソッドで実行することがよくあります。実行時には、メソッド内の変数のスコープは、メソッドが定義されたときのスコープに基づきます。実際、ここで私が言いたいのは、メソッドを実行する場所がどれほど複雑であっても、メソッド内の変数にアクセスできるかどうかの最終判断は、メソッドが定義された場所に戻って検証する必要があるということです。 ]
3 スコープ (スコープ) ルールの設定
a 関数宣言および匿名関数式の場合、[スコープ] は作成時のスコープです
b 名前付き関数式の場合、[スコープ] の先頭は a新しい JS オブジェクト (つまり、Object.prototype を継承します)。このオブジェクトには 2 つのプロパティがあります。1 つ目はそれ自体の名前で、2 つ目は関数内のコードがアクセスできるようにするために定義された関数名です。関数名自体をエラーなしで再帰的に実行します。
3. 実行環境とアクティブなオブジェクト
構文解析が完了し、コードの実行が開始されます。各メソッドを呼び出すと、JS エンジンはメソッド インスタンスのライフ サイクルと一致する実行環境とアクティブ オブジェクトを自動的に確立し、上記のメソッドの実行に必要な実行サポートを提供します。次のように、アクティブ オブジェクトが均一に確立されます (理論的には、アクティブ オブジェクトはメソッドの実行時に生成されます。デモンストレーションの便宜上、ここではすべてのメソッドのアクティブ オブジェクトを一度に定義します):
実行環境
コードをコピー
コードは次のとおりです:
/**
* 実行環境: 関数の実行時に作成される実行環境
*/
var ExecutionContext = {
window: {
type: "global",
name: "global",
body: ActiveObject.window
},
a:{
type: "function",
name: "a",
body: ActiveObject.a,
scopeChain: this .window.body
},
b:{
type: "function",
name: "b",
body: ActiveObject.b,
scopeChain: this.a.body
},
c:{
type: "function",
name: "c",
body: ActiveObject.c,
scopeChain : this.b.body
},
d:{
type: "function",
name: "d",
body: ActiveObject.d,
scopeChain: this.b.body
}
}
上記の各メソッドの実行環境には、対応するメソッドの種類 (関数)、メソッド名 (funcName)、およびアクティブなメソッドが格納されます。オブジェクト (ActiveObject)、スコープ チェーン (scopeChain) およびその他の情報の重要なポイントは次のとおりです:
body 属性、現在のメソッドのアクティブ オブジェクトを直接指します
scopeChain 属性、スコープ チェーン、はリンクされたリスト構造です。解析ツリー内の現在のメソッドに対応するスコープ属性に従って、スコープに対応するメソッドのアクティブなオブジェクト (ActivceObject) を指します。変数検索は、このチェーンに従って アクティブオブジェクト
/**
* アクティブ オブジェクト: 関数の実行時に作成されるアクティブ オブジェクトのリスト
*/
var ActiveObject = {
window: {
variables:{
i: { value:1},
j: { value:2},
k: {値:3}
}、
関数:{
a: this.a
}
}、
a:{
変数:{
x: {値:4}
}、
関数:{
b: SyntaxTree.b
}、
パラメータ:{
o: {値: 10}、
p: {値: 20}、
x: this.variables. x、
q: "未定義"
}、
引数:[this.parameters.o,this.parameters.p ,this.parameters.x]
},
b:{
変数:{
y:{ 値:5}
},
関数:{
c: SyntaxTree.c,
d: SyntaxTree.d
},
parameters:{
r:{value:40},
s:{value:50}
},
引数:[this.parameters.r,this.parameters.s]
},
c:{
変数:{
z:{ 値:6}
}、
関数:{}、
パラメータ: {
u:{value:70}
}、
引数:[this.parameters.u]
} 、
d:{
変数:{}、
関数:{}、
パラメータ:{}、
引数:[]
}
}
上記の各アクティブオブジェクトには、対応するメソッドが格納されており、内部変数セット(変数)、組み込み関数セット(関数)、仮パラメータ(パラメータ)、実パラメータ(引数)など実行に必要な情報が格納されています。アクティブオブジェクトの要点
構文解析からアクティブオブジェクトを作成 ツリーコピーメソッドの内部変数セット(変数)と埋込み関数セット(関数)
メソッドの実行を開始し、アクティブなオブジェクトに設定されている内部変数はすべて未定義にリセットされます
仮パラメータ(パラメータ)と実パラメータ(引数)のオブジェクトを作成、同名の実パラメータ、仮パラメータと変数は[参照]関係です
実行メソッド内の代入ステートメントを使用すると、変数セット内の変数に値が代入されます。
変数の検索ルールは、まず現在の実行環境の ActiveObject 内を検索します。見つからない場合は、次に指す ActiveObject 内を検索します。実行環境のScopeChainプロパティにより、グローバルオブジェクト(ウィンドウ)の
メソッドが実行されるまで、内部変数の値はリセットされません。変数が破棄されるタイミングについては、以下の
の寿命を参照してください。メソッド内の変数のサイクルは、メソッド インスタンスへのアクティブな参照があるかどうかによって異なります。そうでない場合、アクティブなオブジェクトは破棄されます。
6 と 7 により、クロージャーが外部変数にアクセスできるようになります。この処理の根本的な原因は次のとおりです。同じ名前の変数と仮パラメータは同じメモリ アドレスを参照するため、2 番目のケースで変更された引数がローカル変数に影響を与える状況が発生します (ケース 4
) [JS エンジンの変数検索ルールに従って、最初に現在の実行環境の ActiveObject を検索します (見つからない場合)。次に、実行環境のプロパティ ScopeChain が指す ActiveObject に沿って、グローバル オブジェクト (ウィンドウ) まで検索します。 ], ということで、4番目は、現在の変数 i の定義は ActiveObject で見つかりましたが、値が「未定義」だったので、「未定義」を直接出力しました
まとめ
以上ですJS をより深く理解し、その応用をよりよく理解するために、クロージャを学習する過程で、語彙範囲をある程度理解して要約するために、一定期間 JS を学習して使用しました。これらと実際の JS 解釈エンジンとのいくつかの違いについて、私はシステム設計者ではなく、新しいフロントエンド開発者の観点からこの問題を分析しているだけなので、JS 開発者がこの問題の範囲を理解するのに役立つことを願っています。方法!