作用域通常是指在指定的時間內,變量存在于一段代碼中。缺乏對作用域的理解可能會導致令人沮喪的調試體驗。作用域的概念是關于我們的代碼中可以訪問到哪些確定的函數或變量,代碼的上下文和執行環境。
在 JavaScript 中,有兩種類型的作用域:全局和局部作用域。
## 全局作用域
第一種作用域是全局作用域。它很容易定義。如果一個變量或函數是_全局的_,那么在程序中的任何地方都可以訪問到它們。在瀏覽器中,全局作用域是?`window`對象。如果在函數外面聲明一個變量,那么這個變量就存在全局對象中。例如:
~~~
var x = 9;
~~~
一旦該變量被定義,則可以被引用為?`window.x`,因為它存在于全局對象中,我們可以簡單的引用它為?`x`。
## 局部作用域
JavaScript 也可以在每個函數體中創建局部作用域。例如:
~~~
function myFunc() {
var x = 5;
}
myFunc();
console.log( x ); // ReferenceError: x is not defined
~~~
由于?`x`?是在?`myFunc()`?中初始化,所以它只能在?`myFunc()`?中被訪問,如果我們試圖在?`myFunc()`?外面訪問?`x`,則會得到一個引用錯誤。
## 注意
如果你忘記使用?`var`?關鍵字聲明變量,那么這個變量會自動變成全局變量。所以這段代碼可以運行:
~~~
function myFunc() {
x = 5;
}
myFunc();
console.log( x ); // 5
~~~
這是一個壞主意。全局變量的值可以被程序的任何部分或者其他腳本更改。這是不期望發生的,因為它會導致無法預料的副作用。
立即調用表達式(IIFE)提供了一個避免全局變量的方式。你會看到許多如 jQuery 的 JavaScript 庫經常使用這種方式:
~~~
(function() {
var jQuery = { /* All my methods go here. */ };
window.jQuery = jQuery;
})();
~~~
將一切包含在一個函數中并立即調用這個函數,這意味著函數中的所有變量都被綁定在_局部作用域_中。在函數結尾部分,你可以通過將?`jQuery`?對象綁定在全局對象?`window`?上,將一些方法和屬性公開出來。了解更多關于立即調用函數表達式,請查看 Ben Alman 的文章?[Immediately-Invoked Function Expression](http://benalman.com/news/2010/11/immediately-invoked-function-expression/)。
因為局部作用域通過函數而工作,任何在另一個函數中定義的函數都可以訪問外部函數里的變量:
~~~
function outer() {
var x = 5;
function inner() {
console.log( x );
}
inner(); // 5
}
~~~
但是?`.outer()`?函數不能訪問?`.inner()`?函數中定義的任何變量。
~~~
function outer() {
var x = 5;
function inner() {
console.log( x );
var y = 10;
}
inner(); // 5
console.log( y ); // ReferenceError: y is not defined
}
~~~
另外,在一個函數中沒有使用?`var`?關鍵字定義的變量不是這個函數的局部變量 - JavaScript 會向上遍歷作用域鏈(最后會到?`window`?對象)尋找之前定義的這個變量。如果這個變量沒有定義,則會在全局中定義該變量,這樣會導致意外的結果。
~~~
// Functions have access to variables defined in the same scope.
var foo = "hello";
var sayHello = function() {
console.log( foo );
};
sayHello(); // "hello"
console.log( foo ); // "hello"
~~~
相同名稱的變量可以在不同作用域中保存不同的值:
~~~
var foo = "world";
var sayHello = function() {
var foo = "hello";
console.log( foo );
};
sayHello(); // "hello"
console.log( foo ); // "world"
~~~
當在一個函數中引用一個外部作用域定義的變量,函數可以訪問在該函數定義之后發生改變的變量值。
~~~
var myFunction = function() {
var foo = "hello";
var myFn = function() {
console.log( foo );
};
foo = "world";
return myFn;
};
var f = myFunction();
f(); // "world"
~~~
這是一個更復雜的作用域例子:
~~~
(function() {
var baz = 1;
var bim = function() {
console.log( baz );
};
bar = function() {
console.log( baz );
};
})();
~~~
在這個實例中,運行:
~~~
console.log( baz ); // baz is not defined outside of the function
~~~
將會得到一個?`ReferenceError`。`baz`?僅僅是在函數中定義,并且沒有暴露在全局作用域中。
~~~
bar(); // 1
~~~
`.bar()`?是在匿名函數中定義的, 但是它沒有使用?`var`?關鍵字定義,這意味著它沒有綁定到局部作用域,而是在全局作用域創建。另外,它可以訪問?`baz`?變量,因為?`.bar()`?是在與?`baz`?相同的作用域定義的,所以它可以訪問變量?`baz`,即使函數外部的其他代碼不可以。
~~~
bim(); // ReferenceError: bim is not defined
~~~
`.bim()`?只在函數中定義的,所以它作為局部變量而不存在于全局對象中。