首頁  >  文章  >  web前端  >  深入理解JavaScript高階之詞法作用域與作用域鏈_基礎知識

深入理解JavaScript高階之詞法作用域與作用域鏈_基礎知識

WBOY
WBOY原創
2016-05-16 17:09:51966瀏覽

主要內容:
1、分析JavaScript的詞法作用域的意義

2、解析變數的作用域鏈

3、變數名提升時什麼

最近在傳智播客講解JavaScript的課程,有不少朋友覺得JavaScript是如此的簡單,但是又如此的不知如何使用,因此我準備了一些內容給大家分享一下.
這個系列主要講解JavaScript的高級部分的內容,包括作用域鏈、閉包、函數調用模式、原型以及面向對象的一些東西. 在這裡不包含JavaScript的基本語法,如果需要了解基礎的同學可以到http://net. itcast.cn裡面去下載免費的影片學習. 好了,廢話不多說,直接進入我們的正題.

一、關於區塊級作用域
說到JavaScript的變數作用域,與咱們平時使用的類C語言不同.
例如C#中下面代碼:

複製程式碼 程式碼如下:

static void Main(string[] args)

static void Main(string[] args> {
 if(true)
 {
  int num = 10;
 }
 System.Console.WriteLine(num);
}

這段程式碼如果進行編譯,是無法通過的,因為"當前上下文中不存在名稱num". 因為這裡
變數的作用域是由花括號限定的,稱為區塊級作用域.

在區塊級作用域下,所有的變數都在定義的花括號內,從定義開始到花括號結束這個
範圍內可以使用. 出了這個範圍就無法存取. 也就是說程式碼

複製程式碼 程式碼如下:

if(true)
{ System.Console.WriteLine(num);
}

這裡可以訪問,因為變數的定義與使用在同一個花括號內.

但是在JavaScript中就不一樣,JavaScript中沒有區塊級作用域的概念.

二、JavaScript中的作用域
在JavaScript中,下面程式碼:

複製程式碼 程式碼如下:
if(true) { var 100 ;
}
alert(num);


運行的結果是彈跳視窗10. 那麼在JavaScript中變數的作用範圍是怎麼限定的呢?

2.1 函數限定變數作用域

在JavaScript中,只有函數可以限定一個變數的作用範圍. 什麼意思呢?
就是說,在JavaScript中,在函數裡面定義的變量,可以在函數裡面被訪問,但是在函數外
無法訪問. 看如下代碼:

複製程式碼 程式碼如下:
var func = function() {


var func = function() {
= 10;
};
try {
 alert(num);
} catch ( e ) {
 alert( e );
}



這段程式碼在運行時,會拋出一個異常,變數num沒有定義. 也就是說,定義在函數中的變數無法

在函數外使用,當然在函數內可以隨意的使用, 即使在賦值之前. 看下面程式碼:複製程式碼
程式碼如下:


var func = function(var func = function( ) {
 alert(num);
 var num = 10;
 alert(num);
};
try {
 func();
} catch ( e>} catch ( e>} ) {
 alert( e );
}

這段程式碼運行後,不會拋出錯誤,彈窗兩次,分別是 undefined 和 10(至於為什麼,下文解釋).

從這裡可以看得出,變數只有在函數中可以被存取. 同理在該函數中的函數也可以存取.

2.2 子域訪問父域

前面說了,函數可以限定變數的作用域,那麼在函數中的函數就成為該作用域的子域. 在子域

中的程式碼可以存取到父域中的變數. 看下面程式碼:

複製程式碼
程式碼如下:


var func = function() {


var func = function() {
= 10;
 var sub_func = function() {
  alert(num);
 }; sub_func();};func(); >

這段程式碼執行得到的結果就是10. 可以看到上文所說的變數存取情況. 但是在子域中存取父域的
程式碼也是有條件的. 如下面程式碼:

複製程式碼 程式碼如下:

var func = function() {
 var;
 var sub_func = function() {
  var num = 20;
  alert(num);
 };
 sub_func();
 };
 sub_func();
;
;


這段程式碼比前面就多了一個"var num = 20;",這句程式碼在子域中,那麼子網域存取父域的情況就發

生了變化,這段程式碼列印的結果是20. 即此時子域訪問的num是子域中的變量,而不是父域中的.

由此可見訪問有一定規則可言. 在JavaScript中使用變量,JavaScript解釋器首先在當前作
用域中搜索是否有該變量的定義,如果有,就是用這個變量;如果沒有就到父域中尋找該變數.

以此類推,直到最頂級作用域,仍然沒有找到就拋出異常"變量未定義". 看下面代碼:複製程式碼
程式碼如下:


(function() {
 var num = 10;
 (function(> var num = 10;
 (function()(function() {
  var num = 20;
  (function(){
   alert(num);
  })()
 })();  })()

 })();
)( >

這段程式碼執行後印出20. 如果將"var num = 20;"去掉,那麼列印的就是10. 同樣,如果再去掉"var num = 10",那麼就會出現未定義的錯誤.
三、作用域鏈

有了JavaScript的作用域的劃分,那麼可以將JavaScript的存取作用域連成一個鍊式樹狀結構.

JavaScript的作用域鏈一旦能清晰的了解,那麼對於JavaScript的變數與閉包就是非常清晰的了. 下面採用繪圖的辦法,繪製作用域鏈.

3.1 繪製規則:

1) 作用域鏈就是物件的陣列
2) 全部script是0級鏈,每個物件佔一個位置

3) 凡是看到函數延伸一個鏈出來,一級級展開

4) 訪問首先看當前函數,如果沒有定義往上一級鏈檢查5) 如此往復,直到0級鏈

3.2 舉例

看下面程式碼:

複製程式碼

程式碼如下:


var num = 10;


var num = 10;
 var num = 20;
 var func2 = function() {
  var num = 30;
  alert(num);
 } 🎜>};
var func2 = function() {
 var num = 20;
 var func3 = function() {
  alert(num);
 };};
func1();func2();




下面分析這段程式碼:
-> 首先整段程式碼是一個全域作用域,可以標記為0級作用域鏈,那麼久有一個陣列
var link_0 = [ num, func1, func2 ];// 這裡用偽代碼描述
-> 在這裡func1和func2都是函數,因此引出兩條1級作用域鏈,分別為
var link_1 = { func1: [ num, func2 ] };// 這裡用偽代碼描述
var link_1 = { func2: [ num, func3 ] };// 這裡用偽代碼描述
-> 第一條1級鏈衍生出2級鏈
var link_2 = { func2: [ num ] };// 這裡用偽代碼描述
-> 第二條1級鏈中沒有定義變量,是一個空鏈,就表示為

var link_2 = { func3: [ ] };-> 將上面程式碼整合一下,就可以將作用域鍊錶示為:複製程式碼

代碼如下:


// 這裡用偽代碼描述
var link = [ // 0級鏈
 num,
 { func1 : [ // 第一條1級鏈
  num,
  { func2 : [ // 2級鏈 
   num
  ] }
 ]},
 {
  num,
  { func3 : [] }
 ]}];

-> 画像で

として表されます。

深入理解JavaScript高階之詞法作用域與作用域鏈_基礎知識

画像: 01_01 スコープ チェーン.bmp

注: チェーン図を表現するには js コードを使用します。強調表示すると非常に明確になります。

このスコープ チェーンの図を使用すると、変数にアクセスする方法を明確に理解できます。
変数を使用する必要がある場合は、まず現在のチェーンで変数を探し、見つかった場合は直接使用します。再度
を検索しません。見つからない場合は、1 レベルのスコープ チェーンを 0 レベルのスコープ チェーンまで検索します。

変数が属するスコープ チェーンのレベルを明確に決定できれば、JavaScript
コードを分析したり、クロージャなどの高度な JavaScript 機能を使用したりすることが (少なくとも私にとっては) 非常に簡単になります。

3. 変数名昇格と関数名昇格

スコープチェーンと変数アクセスルールには、まず、以下の

の JavaScript コードを見てください:

コードをコピーします コードは次のとおりです:
var num = 10;
var func = function () {
alert(num);
var num = 20;
alert(num);
};
func();

実行結果はどうなるでしょうか?考えてみてください、答えはまだ明かしません

まずこのコードを分析しましょう。

このコードには、メンバー num と func を持つレベル 0 スコープ チェーンがあります。したがって、func の下にはメンバー num があります。関数 func が呼び出されると、
変数 num が現在のスコープで定義されていることが検出され、この変数が使用されます。ただし、コード が設定されているため、この時点では num には値が割り当てられません。したがって、最初の出力は未定義で、2 番目の出力は 20 になります。
答えは正解でしたか?

このようなコードを後ろで定義してそれを前で使うのもJavaScriptではよくある問題ですが、この時あたかも最初に変数が定義されているようなものになり、結果は以下のようになります。次のコード:



コードをコピー

コードは次のとおりです。var num = 10; var func = function() {
var num; // ここで定義されているように見えますが、割り当てはありません。 🎜> アラート(番号);
};
func();



この現象は、変数名プロモーションと呼ばれることがよくあります。たとえば、次のコードもあります。



コード


コードは次のとおりです。

var func = function() {
alert("内部関数の呼び出し");
};

func();
};



さて、このコードの結果はどうなるでしょうか?それとも何か違うはずです。それについては読者に任せません。
次の記事で質問に答えます。

これらの違いのため、実際の開発では、C言語の規定と同様に、変数をすべて先頭に記述することを推奨します。 jQuery などの js ライブラリ
でも行われます。


4. 概要



この記事では主に JavaScript の字句スコープとは何なのかを説明し、

スコープ チェーンと変数アクセスを分析する方法について説明します。最後に 1 つの演習を行って終了しましょう。 ! !

次のコードの実行結果を見てください:

コードをコピーします

コードは次のとおりです:

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn