萬丈高樓平地起,學習基礎很重要。前端
執行環境(execution context)是 JavaScript 中最爲重要的一個概念。執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有一個與之關聯的變量對象(variable object),環境中定義的全部變量和函數都保存在這個對象中。雖然咱們編寫的代碼沒法訪問這個對象,但解析器在處理數據時會在後臺使用它。es6
每一個執行環境都有一個執行環境對象。
全局執行環境是最外圍的一個執行環境。在 Web 瀏覽器中,全局執行環境被認爲是 window 對象。瀏覽器
全局執行環境直到應用程序退出,例如關閉網頁或瀏覽器時纔會被銷燬。ide
每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。函數
在函數執行以後,棧將其環境彈出,把控制權返回給以前的執行環境。ECMAScript 程序中的執行流正是由這個方便的機制控制着。oop
做用域能夠分爲:學習
全局變量擁有全局做用域。變量和函數會掛載到 window 對象上。code
var scope = 'global'; // 聲明一個全局變量 function checkScope () { var scope = 'local'; // 聲明一個同名局部變量 myscope = 'local'; return scope; // 返回局部變量的值, 而不是全局變量的值 } window.myscope // undefined => checkScope() 還未執行,該變量未聲明 checkScope(); // local window.myscope // local
局部變量是局部做用域,僅在函數體內有用。對象
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 並未被覆蓋
函數體內局部變量優先級高於同名全局變量。同名全局變量會被覆蓋。ip
scope = 'global'; // 聲明一個全局變量,能夠不用 var 聲明 function checkScope2 () { scope = 'local'; // 修改了全局變量 scope myscope = 'local'; // 顯式聲明瞭一個新的全局變量 return [scope, myscope]; } checkScope2(); // [local, local] window.scope; // local => 全局變量修改了 window.myscope; // local =>全局命名空間搞亂了
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();
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 沒有塊級做用域。使用不當會形成不合理場景
列舉的例子進行修改。
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
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 中調用函數傳遞參數都是值傳遞的特色
for (let k = 0; k < 5; k++) { setTimeout(function () { console.log(k); }, 1000, k); } // 依次輸出 0 1 2 3 4
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)。
上面的說明是從《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()