この記事では、JavaScript スコープ チェーン (スコープ チェーン) の使用法を例とともに分析します。参考のために皆さんと共有してください。詳細は次のとおりです:
js のスコープ チェーンについては長い間聞いており、いくつかの入門ブログ投稿を読んできましたが、私の理解は常に曖昧でした。最近、「JavaScriptを理解する」という本をじっくり読んだのですが、「コードの空間と時間」の章でスコープチェーンについてほんの数文字で紹介している箇所があり、とても印象に残りました。終わりのない余韻(実際にはまだ理解できません^_^)。次に、読書メモを整理し、オンライン リソースを利用して書き留めます。
1. 簡単な質問から始めましょう
次の JS コードをページで実行すると、どのような結果が表示されますか:
var arg = 1; function fucTest(arg) { alert(arg); var arg = 2; //alert(arg); } fucTest(10);
あなたの答えは何ですか?そうです、10がポップアップします。私の理解では、funTest 関数には仮パラメータ arg があり、funTest 関数は実際のパラメータ 10 を渡し、alert メソッドは恥ずかしいことに 10 をポップアウトするだけです。
さて、ここで再び質問です:
var arg = 1; function funcTest() { alert(arg); var arg = 2; } arg = 10; funcTest();
答えは何ですか? 5 年前の私だったら、間違いなくそれ以上考えません。まだ 10 年です。なぜこのような単純な質問について考える必要があるのでしょうか?私の理解では、funTest 関数はパラメータのない関数であり、関数の実行前に、外部 (グローバル) 変数 arg がalert メソッドを通じて呼び出されます。ポップアップすると、引数の値が 2. に変更されるため、ポップアップ値は 10 になります。
本当に10ですか?はい、もしくは、いいえ?
テスト結果:「未定義」がポップアップ表示され、滝汗。
2. JavaScript の操作メカニズムから始めてスコープ チェーンを理解します
1. js の実行シーケンス
ドキュメント フローに複数のスクリプト コード セグメント (スクリプト タグで区切られた JS コード、または導入された JS ファイル) が含まれる場合、それらの実行順序は次のとおりです:
ステップ 1. 最初のコードセグメントを読み取ります (js 実行エンジンはプログラムを 1 行ずつ実行するのではなく、プログラムを部分ごとに分析して実行します)
ステップ 2. 構文分析を実行します。エラーがある場合は、構文エラー (括弧の不一致など) が報告され、ステップ 5
に進みます。
ステップ 3. var 変数と関数定義の「事前解析」を実行します (正しい宣言のみが解析されるため、エラーは報告されません)
ステップ 4. コードセグメントを実行し、エラーがある場合はエラーを報告します (変数が未定義であるなど)
ステップ 5. 別のコードセグメントがある場合は、次のコードセグメントを読み取り、ステップ 2 を繰り返します
ステップ 6. 終了
上記の分析は、ステップ 2、3、および 4 の赤いフォント、特にステップ 3 の「事前解析」を理解するには盲点である可能性があります。 -解析は、いつも不安を感じます。 4 番目のステップ「エラーがある場合はエラーを報告する」もよく遭遇します。例:
function funcTest() { alert(arg); var arg = 2; } funcTest();
上面这段代码执行时,弹出“undefined”,也就是说arg没有定义,js的变量不是不用定义也可以吗?
2、语法分析和“预解析”
(1)、从解释型语言的编译过程说起
众所周知,javascript是解释型语言,它不同于c#和java等编译型语言。对于传统编译型语言来说,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成;但对于解释型语言来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。
a、词法分析
简单地说,词法分析是将字符流(char stream)转换为记号流(token stream)。
但是这个转换过程并不是可以用一句话就可以概括的那么简单,我们可以试着用伪代码理解一段简单的程序:
代码var result=x-y;的转换大致可以表示如下:
NAME "result"
EQUALS
NAME "x"
MINUS
NAME "y"
SEMICOLON
b、语法分析
简单地说,语法分析就是为了构造合法的语法分析树,而语法分析树可以直观地表示出推导的过程。
那么什么是语法分析树?简单地说,就是程序推导过程的描述。但是到底什么是语法树,请参考专业文章,本篇略过。
c、其他
通过语法分析,构造出语法分析树后,接下来还可能需要进一步的语义检查。对于传统强类型语言来说,语义检查的主要部分是类型检查,比如函数的实参和形参类型是否匹配等等。
结论:通过上面的分析可以看出,对于javascript引擎来说,肯定有词法分析和语法分析,之后可能还有语义检查、代码优化等步骤,等这些编译步骤完成之后(任何语言都有编译过程,只是解释型语言没有编译成二进制代码),才会开始执行代码。
(2)、执行过程
a、javascript的作用域机制
通过编译,javascript代码已经翻译成了语法树,然后会立刻按照语法树执行。
进一步的执行过程,需要理解javascript的作用域机制:词法作用域(lexcical scope)。通俗地讲,就是javascript变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,编译器通过静态分析就能确定,因此词法作用域也叫做静态作用域(static scope)。但需要注意,with和eval的语义无法仅通过静态技术实现,所以只能说javascript的作用域机制非常接近词法作用域(lexical scope).
javascript引擎在执行每个函数实例时,都会创建一个执行环境(execution context)。执行环境中包含一个调用对象(call object), 调用对象是一个scriptObject结构(scriptObject是与函数相关的一套静态系统,与函数实例的生命周期保持一致),用来保存内部变量表varDecls、内嵌函数表funDecls、父级引用列表upvalue等语法分析结构(注意varDecls和funDecls等信息是在语法分析阶段就已经得到,并保存在语法树中。函数实例执行时,会将这些信息从语法树复制到scriptObject上)。
b、javascript作用域机制的实现方法
词法作用域(lexical scope)是javascript的作用域机制,还需要理解它的实现方法,就是作用域链(scope chain)。作用域链是一个name lookup机制,首先在当前执行环境的scriptObject中寻找,没找到,则顺着upvalue到父scriptObject中寻找,一直lookup到全局调用对象(global object)。
现在回过头来分析第二个问题:
var arg = 1; function funcTest() { alert(arg); var arg = 2; } arg = 10; funcTest();
在执行funcTest函数时,也即进入了funcTest对应的作用域,js引擎在执行时,当遇到对变量名或者函数名的使用时,会首先在当前作用域(也即funcTest对应的作用域)查找变量或者函数(显然,arg变量在funcTest对应的作用域里被定义为var arg=2 所以alert方法的参数采用的是当前作用域的arg,但是因为arg被定义在alert方法后,所以arg变量默认值为undefined)。当然,如果没有找到就到上层作用域查找,依此类推(作用域范围可以持续到javascript运行环境的根:window对象)。
最后,让你看的更清楚,上面的代码其实可以等价于:
var arg = 1; function funcTest() { var arg; //默认值undefined alert(arg); arg = 2; } arg = 10; funcTest();
c、終了
関数インスタンスが実行されると、クロージャが作成されるか、クロージャに関連付けられます。 (クロージャについてはまた勉強記を書く予定です)
scriptObject は関数に関連する変数テーブルを静的に保存するために使用され、クロージャは実行中にこれらの変数テーブルとその実行値を動的に保存します。
クロージャのライフサイクルは、関数インスタンスのライフサイクルよりも長い場合があります。関数インスタンスは、アクティブな参照が空になると自動的に破棄されます。
クロージャは、データ参照が空になった後、JavaScript エンジンによってリサイクルされます (場合によっては、自動的にリサイクルされず、メモリ リークが発生します)。追記: 「実行プロセス」に関するこのセクションは発音が少し難しいですが、実行コンテキスト、呼び出しオブジェクト、語彙スコープ、スコープなどの概念を理解したら、怖がらないでください。チェーンとクロージャーは、JavaScript の多くの現象を簡単に解決できます。
3. 結論
2 番目の段落の分析を通じて、最初の段落で著者が行った判断と比較してください (また、著者のこれまでの分析と結論が甘いとも思いますか (たとえ結果がたまたま正しい場合もあるとしても)) ?! 表面的なものではありません、^_^) JavaScript には非常に多くの「謎」があることがわかりますが、それを真に理解して習得するのは簡単ではありません。まずは「理解」してから話しましょう。この記事が JavaScript プログラミングのすべての人に役立つことを願っています。