JavaScript溫故而知新——執行環境和做用域

1、全局對象

咱們都知道JavaScript中有一類很是重要的對象——全局對象(global object),它的屬性是全局定義的符號,編寫JavaScript代碼時咱們能夠直接對這些屬性進行使用。當JavaScript解釋器啓動時(瀏覽器加載新頁面的時候),它將建立一個新的全局對象,而且定義一組初始的屬性:
瀏覽器

  • 全局屬性,如undefinedInfinityNaN
  • 全局函數,如isNaN()parseInt()eval()
  • 構造函數,如Date()RegExp()String()Object()Array()
  • 全局對象,如MathJSON

在代碼的最頂級——不在任何函數內的JavaScript代碼中,能夠使用this來引用全局對象:bash

var global = this;  // 定義一個引用全局對象的全局變量
複製代碼

在瀏覽器中,Window對象充當了全局對象,它的window屬性引用其自身,能夠代替this來引用全局對象。Window對象還針對Web瀏覽器和客戶端JavaScript定義了額外的全局屬性。另外,當咱們在代碼中聲明瞭一個全局變量,這個全局變量就是全局對象的一個屬性。閉包

2、執行環境

執行環境能夠說是JavaScript中最爲重要的一個概念。app

執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。函數

能夠這麼理解,每一個執行環境都有一個與之關聯的變量對象,咱們在環境中定義的全部變量和函數都會保存在這個對象中,不過這個對象咱們是沒法訪問的,它只供解析器在處理數據時在後臺使用它。
post

全局執行環境——最外圍的一個執行環境,在瀏覽器中,全局執行環境即全局對象window,所以全部全局變量和函數均可以做爲window對象的屬性和方法;
函數的執行環境——每個函數都本身的執行環境,當代碼執行到一個函數時,這個函數的環境就會被推入到一個環境棧中,函數執行完畢後,環境棧會將它的執行環境彈出,並將控制權返回給以前的執行環境。JavaScript代碼的執行順序即是由這一機制所控制着。
垃圾收集——當環境棧將一個執行環境彈出後,該環境便會被銷燬,保存在其中的全部變量和函數定義也會隨之銷燬進而釋放內存,這即是JavaScript的自動垃圾收集機制。而全局執行環境只有在程序退出,例如關閉網頁時纔會被銷燬,所以對於全局變量或者全局對象的屬性,一旦咱們再也不須要用到它們的時候,能夠手動將其值設置爲null來解除其引用,一旦引用被解除,它們便會脫離執行環境,以便垃圾收集器運行時將其回收,這樣作能起到很好的優化內存佔用的效果。優化

3、做用域

1.變量做用域

一個變量的做用域指在代碼中定義這個變量的區域,全局變量擁有全局做用域,在任何地方都有定義。
而在函數內聲明的變量只在函數體內有定義,它們是局部變量,做用域是局部性的。函數參數也是局部變量,只在函數體內有定義。
在函數體內,局部變量優先級高於全局變量,若是在函數內聲明的變量或函數參數中帶有的變量和全局變量重名,則全局變量會被局部變量所覆蓋。
聲明局部變量必須使用var語句ui

scope = "global";
function checkscope () {
    scope = "local";    // 修改了全局變量
    myscope = "local";  // 顯示的聲明瞭新的全局變量
    return [scope, myscope];
}
checkscope();   // ["local", "local"]
console.log(scope, myscope)     // "local","local"
複製代碼

2.函數做用域

在其餘相似於C之類的語言中,花括號封閉的代碼塊都有本身的做用域,至關於JS中的執行環境。而JS中是沒有塊級做用域的,JS取而代之使用的是函數做用域,即變量在聲明它們的函數體以及這個函數體內嵌套的任意函數體內都是有定義的。this

function test(o) {
    if (o) {
        var i = o;                      // i在函數體內是有定義的,不只是在if這個代碼段內
        for(var j = 0; j < 10; j++) {   // j在函數體內是有定義的,不只是在for循環內
            console.log(j);             // 輸出0~9
        }
        console.log(j);                 // j已經定義了,輸出10
    }
    console.log(i);                     // i已經定義了,但可能沒有初始化
}
複製代碼

3.聲明提早

咱們知道了在JavaScript的函數做用域下,函數內聲明的全部變量在函數體內是始終可見的,因爲這一特性咱們可能會出現一些誤解。先看一段代碼:spa

var scope = "global";
function f() {
    console.log(scope);     // 輸出"undefined",而不是"global"
    var scope = "local";    // 變量在這裏賦初始值,但變量自己在函數體內任何地方都是有定義的
    console.log(scope);     // 輸出"local"
}
複製代碼

你可能會誤覺得函數中第一行輸出global,但結果並不是如此,這就是容易形成咱們誤解的地方。因爲函數做用域的特性,局部變量在整個函數體始終是有定義的,這意味着變量在聲明以前就能夠使用了。
這個特性被稱做:"聲明提早"——即函數內的變量聲明會被「提早」到函數體頂部,同時變量初始化留在原來的位置。
所以上面函數的代碼實際上等價於:

function f() {
    var scope;              // 在函數頂部聲明瞭局部變量
    console.log(scope);     // 變量存在,但其值是"undefined"
    scope = "local";        // 到這裏纔對變量進行初始化並賦值
    console.log(scope);
}
複製代碼

注意點

  • "聲明提早"是在JavaScript引擎的"預編譯"階段進行的,即代碼開始運行以前。
  • 因爲JavaScript沒有塊級做用域,所以一般將變量聲明放在函數體頂部,這使得咱們的代碼能更加清晰的反映出真實的變量做用域。

結尾

系列文章:

相關文章
相關標籤/搜索