學了好久的 Javascript 慚愧仍然沒有總結完全作用域,今天老夫就來總結一番:
涉及內容:
- 全域性作用域
- 函式作用域
- 塊級作用域
- 詞法作用域
- 動態作用域 動態作用域跟 this 引用機制相關
全域性作用域:
作用域,是指變數的生命週期(一個變數在哪些範圍內保持一定值)。
全域性變數:
生命週期將存在於整個程式之內。
能被程式中任何函式或者方法訪問。
在 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
複製程式碼
通過 return 訪問函式內部變數:
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
引用!!!
參考和鳴謝:
- 《你不知道的JavaScript》
- 《JavaScript函數語言程式設計》
首先,感謝這兩本書的作者,我也算是結合自己的例項和理解,好好地總結了一下作用域;