學了很久的 Javascript 慚愧仍然沒有總結徹底做用域,今天老夫就來總結一番:javascript
涉及內容:java
- 全局做用域
- 函數做用域
- 塊級做用域
- 詞法做用域
- 動態做用域 動態做用域跟 this 引用機制相關
做用域,是指變量的生命週期(一個變量在哪些範圍內保持必定值)。程序員
全局變量:面試
生命週期將存在於整個程序以內。編程
能被程序中任何函數或者方法訪問。bash
在 JavaScript 內默認是能夠被修改的。閉包
全局變量,雖然好用,可是是很是可怕的,這是全部程序員公認的事實。函數式編程
帶有關鍵字 var 的聲明;函數
<script type="text/javascript">
var testValue = 123;
var testFunc = function () { console.log('just test') };
/**---------全局變量會掛載到 window 對象上------------**/
console.log(window.testFunc) // ƒ () { console.log('just test') }
console.log(window.testValue) // 123
</script>
複製代碼
其實,咱們寫的函數若是不通過封裝,也會是全局變量,他的生命週期也就是全局做用域;學習
不帶有聲明關鍵字的變量,JS 會默認幫你聲明一個全局變量!!!
<script type="text/javascript">
function foo(value) {
result = value + 1; // 沒有用 var 修飾
return result;
};
foo(123); // 124
console.log(window.result); // 124 <= 掛在了 window全局對象上
</script>
複製代碼
如今,變量 result
被掛載到 window
對象上了!!!
函數做用域內,對外是封閉的,從外層的做用域沒法直接訪問函數內部的做用域!!!
function bar() {
var testValue = 'inner';
}
console.log(testValue); // 報錯:ReferenceError: testValue is not defined
複製代碼
function bar(value) {
var testValue = 'inner';
return testValue + value;
}
console.log(bar('fun')); // "innerfun"
複製代碼
函數就像一個工廠,咱們輸入一些東西,它在內部加工,而後給咱們一個加工產物;
function bar(value) {
var testValue = 'inner';
var rusult = testValue + value;
function innser() {
return rusult;
};
return innser();
}
console.log(bar('fun')); // "innerfun"
複製代碼
關於閉包,我不會在這篇文章過多描述,由於,想要描述閉包,自己須要跟本文章同樣的長度;
這是個很實用的函數,不少庫都用它分離全局做用域,造成一個單獨的函數做用域;
<script type="text/javascript">
(function() {
var testValue = 123;
var testFunc = function () { console.log('just test'); };
})();
console.log(window.testValue); // undefined
console.log(window.testFunc); // undefined
</script>
複製代碼
它可以自動執行 (function() { //... })()
裏面包裹的內容,可以很好地消除全局變量的影響;
在 ES6 以前,是沒有塊級做用域的概念的。若是你有 C++ 或者 Java 經驗,想必你對塊級做用域並不陌生;
for(var i = 0; i < 5; i++) {
// ...
}
console.log(i) // 5
複製代碼
很明顯,用 var 關鍵字聲明的變量,在 for
循環以後仍然被保存這個做用域裏;
這能夠說明: for() { }
仍然在,全局做用域裏,並無產生像函數做用域同樣的封閉效果;
若是想要實現 塊級做用域 那麼咱們須要用 let
關鍵字聲明!!!
for(let i = 0; i < 5; i++) {
// ...
}
console.log(i) // 報錯:ReferenceError: i is not defined
複製代碼
在 for
循環執行完畢以後 i 變量就被釋放了,它已經消失了!!!
一樣能造成塊級做用域的還有 const
關鍵字:
if (true) {
const a = 'inner';
}
console.log(a); // 報錯:ReferenceError: a is not defined
複製代碼
let
和 const
關鍵字,建立塊級做用域的條件是必須有一個 { }
包裹:
{
let a = 'inner';
}
if (true) {
let b = 'inner';
}
var i = 0;
// ......
複製代碼
不要小看塊級做用域,它能幫你作不少事情,舉個栗子:
舉一個面試中常見的例子:
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 5 5 5 5 5
}, 200);
};
複製代碼
這幾乎是做用域的必考題目,你會以爲這種結果很奇怪,可是事實就是這麼發生了;
這裏的 i 是在全局做用域裏面的,只存在 1 個值,等到回調函數執行時,用詞法做用域捕獲的 i 就只能是 5;
由於這個循環計算的 i 值在回調函數結束以前就已經執行到 5 了;咱們應該如何讓它恢復正常呢???
解法1:調用函數,建立函數做用域:
for(var i = 0; i < 5; i++) {
abc(i);
};
function abc(i) {
setTimeout(function() {
console.log(i); // 0 1 2 3 4
}, 200);
}
複製代碼
這裏至關於建立了5個函數做用域來保存,咱們的 i 值;
解法2:採用當即執行函數,建立函數做用域;
for(var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 200);
})(i);
};
複製代碼
原理同上,只不過換成了自動執行這個函數罷了,這裏保存了 5 次 i 的值;
解法3:let
建立塊級做用域
for(let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 200);
};
複製代碼
詞法做用域是指一個變量的可見性,及其文本表述的模擬值(《JavaScript函數式編程》);
聽起來,十分地晦澀,不過將代碼拿來分析就很是淺顯易懂了;
testValue = 'outer';
function afun() {
var testValue = 'middle';
console.log(testValue); // "middle"
function innerFun() {
var testValue = 'inner';
console.log(testValue); // "inner"
}
return innerFun();
}
afun();
console.log(testValue); // "outer"
複製代碼
當咱們要使用聲明的變量時:JS引擎總會從最近的一個域,向外層域查找;
再舉一個一個實際的例子:
var testValue = 'outer';
function foo() {
console.log(testValue); // "outer"
}
function bar() {
var testValue = 'inner';
foo();
}
bar();
複製代碼
顯然,當 JS 引擎查找這個變量時,發現全局的 testValue 離得更近一些,這剛好和 動態做用域 相反;
如上圖所示,下面將講述與 詞法做用域相反的動態做用域;
在編程中,最容易被低估和濫用的概念就是動態做用域(《JavaScript函數式編程》)。
在 JavaScript 中的僅存的應用動態做用域的地方:this
引用,因此這是一個大坑!!!!!
動態做用域,做用域是基於調用棧的,而不是代碼中的做用域嵌套;
做用域嵌套,有詞法做用域同樣的特性,查找變量時,老是尋找最近的做用域;
一樣是,詞法做用域,例子2,同一份代碼,若是 是動態做用域:
var testValue = 'outer';
function foo() {
console.log(testValue); // "inner"
}
function bar() {
var testValue = 'inner';
foo();
}
bar();
複製代碼
固然,JavaScript
除了this
以外,其餘,都是根據詞法做用域查找!!!
爲何要理解動態做用域呢?由於,這能讓你更好地學習 this
引用!!!
首先,感謝這兩本書的做者,我也算是結合本身的實例和理解,好好地總結了一下做用域;