作用域
作用域即代碼片段的有效范圍,這里的代碼片段可以是一個函數、一個變量等。
在 JavaScript 中,通常被拿來討論的是 全局作用域
和 函數作用域
。
1. 全局作用域
在全局環境下定義的變量、函數,都屬于全局作用域的范圍,也就是這些變量、函數在任何地方都能被訪問。
var number = 1;
var fn = function() {
console.log('我是一個全局下的函數');
console.log('訪問全局下的 number: ', number);
};
fn();
全局的作用域很好理解,即全局下的變量、函數在任何地方都能被訪問到。
在 ES6 的模塊化規范中,一個模塊就是一個 js 文件,所以如果在 ES6 的模塊的最頂層聲明變量和函數,是不屬于全局的。
2. 函數作用域
函數擁有自己的作用域,也就是說函數內聲明的變量和函數,只在函數內有效。
var fn = function() {
var innerFn = function() {
console.log('我是函數內的函數');
};
var str = '我是函數內的變量';
innerFn();
console.log(str);
};
fn();
console.log(str); // 輸出:ReferenceError: str is not defined
函數內的變量 str
在函數外部無法訪問到,因為其所在的作用域是函數 fn
的作用域,所以只在 fn
函數內能被訪問。
3. eval 的作用域
eval 根據調用的方式,其作用域也會發生變化。
如果直接調用 eval
則其作用域就是當前上下文中的作用域。如果間接性質的調用,比如通過一個 eval
的引用來調用,那作用域就是全局的。
var storeEval = eval;
(function() {
storeEval('var number1 = 1;');
eval('var number2 = 2');
console.log('自執行匿名函數內輸出:', number2);
})();
console.log(number1); // 輸出:1
console.log(number2); // 輸出:ReferenceError: number2 is not defined
本身 eval 用到的情況就少,所以這種情況下做個了解即可。
4. 作用域鏈
理解了作用域,作用域鏈就很好理解了。
通常情況下,內層作用域擁有訪問上層作用域的能力,而外層無法訪問到內層的作用域。
var number = 1;
var fn = function() {
console.log(number);
var str = '字符串';
};
fn();
console.log(str); // 輸出:ReferenceError: str is not defined
由此可見作用域從內往外的。
var number1 = 1;
var fn1 = function() {
var number2 = 2;
var fn2 = function() {
var number3 = 3;
var fn3 = function() {
console.log('fn3 內的輸出:');
console.log(number1);
console.log(number2);
console.log(number3);
}
fn3();
};
fn2();
}
fn1();
例子中的 fn3
就具有訪問 fn2作用域
、fn1作用域
、全局作用域
的能力。
這樣從內往外就形成了一條作用域鏈。
5. 利用函數作用域進行封裝
函數作用域最常用的場景之一就是隔離作用域。
因為函數有自己的作用域,所以很多庫、框架在實現的時候都會把內容寫在一個函數中。
(function() {
var config = {};
var fn = function() {
// ...
};
window.$ = fn;
window.jQuery = fn;
})();
這樣就不會污染到全局,只對外暴露想要暴露的部分。
6. 小結
有關作用域有更深入的內容,本篇探討的是最容易理解的部分。
理解作用域可以更好的組織代碼結構,減少各個上下文的污染。
在 ES6 中引入了塊及作用域的概念,這是在之前都沒有的,可以查閱 ES6 中對應的內容進行了解。