JavaScript基礎—深刻了解做用域與做用域鏈

前言

萬丈高樓平地起,學習基礎很重要。前端

前置知識

執行環境

執行環境(execution context)是 JavaScript 中最爲重要的一個概念。執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的全部變量和函數都保存在這個對象中。雖然咱們編寫的代碼沒法訪問這個對象,但解析器在處理數據時會在後臺使用它。es6

每一個執行環境都有一個執行環境對象。

全局執行環境

全局執行環境是最外圍的一個執行環境。在 Web 瀏覽器中,全局執行環境被認爲是 window 對象。瀏覽器

全局執行環境直到應用程序退出,例如關閉網頁或瀏覽器時纔會被銷燬。ide

函數執行環境

每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。函數

在函數執行以後,棧將其環境彈出,把控制權返回給以前的執行環境。ECMAScript 程序中的執行流正是由這個方便的機制控制着。oop

做用域

做用域能夠分爲:學習

  • 全局做用域
  • 局部做用域
  • 塊級做用域(es6)

1.全局做用域

全局變量擁有全局做用域。變量和函數會掛載到 window 對象上。code

var scope = 'global'; // 聲明一個全局變量
function checkScope () {
  var scope = 'local'; // 聲明一個同名局部變量
  myscope = 'local';
  return scope;        // 返回局部變量的值, 而不是全局變量的值
}

window.myscope // undefined => checkScope() 還未執行,該變量未聲明
checkScope();  // local
window.myscope // local

2.局部做用域

局部變量是局部做用域,僅在函數體內有用。對象

var scope = 'global';
function checkScope () {
  var scope = 'local';
  function nested() {
    var scope = 'nested';
    return scope;
  }
  return nested();
}

checkScope() // nested =>返回的是 nested() 的 scope 
window.scope  // global => 全局變量 scope 並未被覆蓋

3. 做用域優先級

函數體內局部變量優先級高於同名全局變量。同名全局變量會被覆蓋。ip

scope = 'global';    // 聲明一個全局變量,能夠不用 var 聲明
function checkScope2 () {
  scope = 'local';   // 修改了全局變量 scope
  myscope = 'local'; // 顯式聲明瞭一個新的全局變量
  return [scope, myscope];
}

checkScope2(); // [local, local] 
window.scope; // local => 全局變量修改了
window.myscope; // local =>全局命名空間搞亂了

4. 同名變量

var 聲明變量會提高,內部變量可能會覆蓋外層變量

var tmp = '哈哈';

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}
f(); // undefined => 理想狀況應該輸出值 「 哈哈 」

緣由在於,預編譯後,if 語句內的 temp 聲明提高了

var tmp = '哈哈';

function f() {
  var tmp 
  console.log(tmp); // 打印的是 if 裏面提高 temp
  if (false) {
    tmp = 'hello world';
  }
}
f();

5.ES5模擬塊級做用域

ES5 沒有塊級做用域。使用不當會形成變量泄露。

for (var k = 0; k < 5; k++) {
  setTimeout(function () {
      console.log('inside', k);
  }, 1000);
}
   
console.log('outside', k); // outside 5  => 理想狀況下,k 僅在 for 循環中有效,這裏不該該輸出 5,應該提示 k is not defined
// 間隔1s,分別輸出5個 inside 5 => 理想狀況下,應該輸出 0 1 2 3 4

window.k; // 5 => 可看出 k 是全局變量,因此當執行 for 裏面的語句時,k已經循環完了5次,此時 k = 5

再來一題

var test = function() {
  var arr = [];
  for (var i = 0; i < 3; i++) {
    console.log('開始循環了', i)
    arr[i] = function() {
      return i * i;
    };
  }
  return arr;
};
 
var a = test(); // 輸出 「開始循環了 0 1 2」 => 此時 arr[i]是還未執行的,i 已經等於 3 了
a[1](); // 9
a[2](); // 9

塊級做用域

經過前面的介紹,能夠知道,ES5 是沒有塊級做用域的。變量使用不當,容易形成變量泄露,出現不少不合理場景。爲了不這種狀況出現,咱們可使用如下方法:

如下都是針對 ES5 沒有塊級做用域。使用不當會形成不合理場景列舉的例子進行修改。

1. 藉助當即執行函數

for (var k = 0; k < 5; k++) { 
  (function(k){
    //這裏是塊級做用域
    setTimeout(function (){
      console.log('inside', k);
     },1000);
  })(k);
}

console.log('outside', k);
// 輸出 outside 5
// 再依次輸出 inside 0 1 2 3 4

2. 定義函數並傳值

var _loop = function _loop(k) {
  //這裏是塊級做用域
  setTimeout(function () {
    console.log(k);
  }, 1000);
};

for (var k = 0; k < 5; k++) {
  _loop(k);
}
// 依次輸出 0 1 2 3 4

一、2 寫法都是利用了 JS 中調用函數傳遞參數都是值傳遞的特色

3. 使用setTimeout的第三個參數

for (let k = 0; k < 5; k++) {
  setTimeout(function () {
      console.log(k);
  }, 1000, k);
}
// 依次輸出 0 1 2 3 4

4. 使用 let、const 聲明變量

for (let k = 0; k < 5; k++) {
  setTimeout(function () {
      console.log(k);
  }, 1000);
}
   
console.log(k); // k is not defined
// 間隔1s,分別輸出inside 0 1 2 3 4
關注執行順序

做用域鏈

當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈(scope chain)。

  • 做用域鏈的用途,是保證對執行環境有權訪問的全部變量和函數的有序訪問。
  • 做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象(activation object)做爲變量對象。活動對象在最開始時只包含一個變量,即 arguments 對象(這個對象在全局環境中是不存在的)。
  • 做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境。
  • 全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。

上面的說明是從《JavaScript權威指南》指南中摘抄出來的。

簡單點總結就是:

當你使用一個變量時,會先從當前做用域找,若是找不到就往上找,一層一層往直到找到全局做用域都還沒找到,就拋出錯誤,說明沒有這個變量。這種一層一層的關係,就是做用域鏈 。

var a = 100
function F1() {
    var b = 200
    function F2() {
        var c = 300
        console.log(a) // 100 順做用域鏈向父做用域找
        console.log(b) // 200 順做用域鏈向父做用域找
        console.log(c) // 300 本做用域的變量
        console.log(d) // ReferenceError:d is not defined 找到window都找不到,變量不存在,報錯
    }
    F2()
}
F1()
相關文章
相關標籤/搜索