JS的做用域

1、前言

做用域、做用域鏈是JavaScript中重要的組成部分和重點知識,是咱們務必要掌握的內容。javascript

若是沒有掌握,那麼做爲重點難點之一的函數的閉包將會難以理解、無從下手。java

2、做用域

1. 函數做用域 [[scope]]

規則:git

  1. 外部對內部可見
  2. 內部對外部不可見
  3. 內部優先
  4. JS中,只有函數級別的做用域,沒有塊級做用域。也就是說,只有在進入或者退出函數時,做用域會發生變化。

2. 代碼解析

  1. 外部對內部可見
var scope = 'global';
function f() {
    console.log(scope); // 'global'
}
f();
複製代碼
  1. 內部對外部不可見
function f2() {
    var scope2 = 'local2';
}
console.log(scope2); // 報錯scope2 is not defined
複製代碼
  1. 均可見時,內部優先
var scope3 = 'global3';
function f3() {
    console.log(scope3); // undefined
    var scope3 = 'local3';
    console.log(scope3); // 'local3'
}
f3();
複製代碼
  1. js做用域都是函數級別的
// 1 if代碼塊
var scope = 'g';
if(true) {
    var scope = 'l';
    console.log(scope); // 'l'
}
console.log(scope); // 'l'

// 2 for代碼塊
for(var i = 0; i < 10; i++) {
    console.log(i); // 0 1 2 3 4 5 6 7 8 9
}

// 3 function函數
function fn() {
    aa = 5;
}
fn();
console.log(aa); // 5
複製代碼

3、執行環境和做用域鏈

1. 執行環境(EC)

執行環境(execution context),也就是執行期上下文,它定義了執行期間能夠訪問的變量和函數。github

  1. 全局執行環境
  • Global Object(Window),即GO
  • 從見到JS代碼開始建立
  • 到網頁關閉時銷燬
  1. 函數執行環境
  • Activation Object(AO)
  • 從函數調用開始建立
  • 到函數調用結束時銷燬

2. 做用域鏈(scope chain)

  1. 做用域鏈[[scope chain]],每一個函數都有
  2. 做用域鏈是私有屬性,只能由JS引擎訪問
  3. 做用域鏈,是AO和GO構成的鏈
  4. 所謂執行環境就是沿着做用域鏈依次查找變量和函數
  • 找到即停
  • 所有找完沒有結果的話,就報錯

3. 生成做用域鏈

規則1:每一個函數在定義(函數聲明、函數表達式)時會拷貝其父級函數的做用域鏈。

規則2:在函數被調用時,生成AO而後將AO壓入做用域鏈的棧頂(數據結構中的棧)。

var g = 'g';
function fa() {
    var a = 'a';
    function fb() {
        var b = 'b';
    }
    fb();
}
fa();
複製代碼

解析:數據結構

  1. 首先生成全局執行環境,進行全局環境的預編譯,併產生做用域鏈,在做用域鏈中存着GO對象。(此時,GO做用域鏈的引用數爲1)
  2. 按照規則1,函數在定義時會首先拷貝其父級函數的做用域鏈,所以在調用fa函數生成fa函數下的AO對象時,fa的做用域鏈中必然已經有一個GO對象的做用域鏈。 另外,fa函數生成的AO對象做用域鏈將被壓入fa函數做用域的棧頂。(此時,GO做用域鏈的引用數爲2,fa函數的AO對象引用數爲1)
  3. 接着,當預編譯fb函數時,首先按照規則1拷貝了其父級的做用域鏈,即fa函數的AO做用域鏈以及全局環境下的GO做用域鏈。最後將本身生成的AO做用域鏈壓入fb函數做用域鏈的棧頂。(此時,GO引用數爲3,fa的AO引用數爲2,fb的AO引用數爲1)
  4. 當fb函數執行完成後,fb的做用域鏈消失,fb的AO被回收。此時,fb的AO引用數爲0,fa的AO引用數爲1,GO引用數爲2。
  5. 當fa函數執行完成後,fa的做用域鏈消失,fa的AO被回收。此時,fa的AO引用數爲0,GO引用數爲1。
  6. 所以,最後只剩下全局執行環境下的GO對象。

規則3 with中,生成的新的with variable object,放在做用域鏈表的最頂端

var name = 1;
var person = {name: 'Nancy'};
with (person) {
    console.log(name); // 'Nancy'
}
var person2 = {
    name2: 'Mike',
    age: 18,
    height: 175,
    wife: {
        name2: 'AA',
        age: 21
    }
}
with (person2.wife) {
    console.log(name2); // 'AA'
    console.log(age); // 21
}
複製代碼

4. 做用域鏈的注意點

  1. 效率:儘可能少使用靠上層的變量,多使用本身的局部變量;
  2. 重名容易出錯:儘可能減小不一樣層次函數使用相同的變量名,避免函數名與變量名同樣;
  3. 閉包:函數執行完成後,AO不必定被釋放,利用這個特色能夠生成閉包。
function outer() {
    var scope = 'outer';
    function inner() {
        return scope;
    }
    return inner;
}
var fn = outer();
console.log(fn()); // 'outer'
複製代碼

4、本節思惟導圖

源碼地址: github.com/Knight174/M…
相關文章
相關標籤/搜索