JavaScript 高級特性 做用域詳解

JavaScript是一種應用很是普遍的語言,其也有一些自身特色和優點,本文重在講述其做用域機制以及閉包,會從一些實例來探討其機理。javascript

做用域在JavaScript程序員平常使用中有不一樣的含義,以下所示:java

  • this綁定的值;
  • this綁定的值定義的執行上下文;
  • 一個變量的「生命週期」;
  • 變量的值解析方案,或詞法綁定。

下面將講訴JavaScript做用域概念,由此引出變量值解析方案的通常想法,最後再探討JavaScript裏閉包這一重要知識點。程序員

1.全局做用域

全部瀏覽器都支持 window 對象,它表示瀏覽器窗口,JavaScript 全局對象、函數以及變量均自動成爲 window 對象的成員。因此,全局變量是 window 對象的屬性,全局函數是 window 對象的方法,甚至 HTML DOM 的 document 也是 window 對象的屬性之一。編程

全局變量是JavaScript裏生命週期(一個變量多長時間內保持必定的值)最長的變量,其將跨越整個程序,能夠被程序中的任何函數方法訪問。瀏覽器

在全局下聲明的變量都會在window對象下,都在全局做用域中,咱們能夠經過window對象訪問,也能夠直接訪問。閉包

1 var name = "小kk";
2 console.log(window.name); // 輸出:小kk
3 console.log(name); // 輸出:小kk

在JS中任何位置,沒有使用var關鍵字聲明的變量也都是全局變量。app

function fun() {
    name = "小kk";
    alert(name);
}

console.log(name); // 輸出:小kk

全局變量存在於整個函數的生命週期中,然而其在全局範圍內很容易被篡改,咱們在使用全局變量時必定要當心,儘可能不要使用全局變量。在函數內部聲明變量沒有使用var也會產生全局變量,會爲咱們形成一些混亂,好比變量覆蓋等。因此,咱們在聲明變量的任什麼時候候最好都要帶上var編程語言

全局變量存在於程序的整個生命週期,但並非經過其引用咱們必定能夠訪問到全局變量。函數

2.詞法做用域

詞法做用域:函數在定義它們的做用域裏運行,而不是在執行它們的做用域裏運行也就是說詞法做用域取決於源碼,經過靜態分析就能肯定,所以詞法做用域也叫作靜態做用域。with和eval除外,因此只能說JS的做用域機制很是接近詞法做用域(Lexical scope)。詞法做用域也能夠理解爲一個變量的可見性,及其文本表述的模擬值。this

var name = "global";

function fun() {
    var name = "小kk";
    return name;
}

console.log(fun()); // 輸出:小kk
console.log(name); // 輸出:global

在一般狀況下,變量的查詢從最近接的綁定上下文開始,向外部逐漸擴展,直到查詢到第一個綁定,一旦完成查找就結束搜索。就像上例,先查找離它最近的name="jeri",查詢完成後就結束了,將第一個獲取的值做爲變量的值。

3.動態做用域

在編程實踐中,最容易低估和過分濫用的概念就是動態做用域,由於不多有語言支持這種方式爲綁定解析方案。

動態做用域與詞法做用域相對而言的,不一樣於詞法做用域在定義時肯定,動態做用域在執行時肯定,其生存週期到代碼片斷執行爲止。動態變量存在於動態做用域中,任何給定的綁定的值,在肯定調用其函數以前,都是不可知的。

在代碼執行時,對應的做用域鏈經常是保持靜態的。然而當遇到with語句、call方法、apply方法和try-catch中的catch時,會改變做用域鏈的。以with爲例,在遇到with語句時,會將傳入的對象屬性做爲局部變量來顯示,使其便於訪問,也就是說把一個新的對象添加到了做用域鏈的頂端,這樣必然影響對局部標誌符的解析。當with語句執行完畢後,會把做用域鏈恢復到原始狀態。實例以下:

var name = "global";

// 使用with以前
console.log(name); // 輸出:global

with({name:"小kk"}){
    console.log(name); // 輸出:小kk
}

// 使用with以後,做用域鏈恢復
console.log(name); // 輸出:global

在做用域鏈中有動態做用域時,this引用也會變得更加複雜,再也不指向第一次建立時的上下文,而是由調用者肯定。好比在使用apply或call方法時,傳入它們的第一個參數就是被引用的對象。實例以下:

function globalThis() {
    console.log(this);
}

globalThis(); // 輸出:Window {document: document,external: Object…}
globalThis.call({name:"jeri"}); // 輸出:Object {name: "jeri"}
globalThis.apply({name:"jeri"},[]); // 輸出:Object {name: "jeri"}

由於this引用是動態做用域,因此在編程過程當中必定要注意this引用的變化,及時跟蹤this的變更。

4.函數做用域

函數做用域,顧名思義就是在定義函數時候產生的做用域,這個做用域也能夠稱爲局部做用域。和全局做用域相反,函數做用域通常只在函數的代碼片斷內可訪問到,外部不能進行變量訪問。在函數內部定義的變量存在於函數做用域中,其生命週期隨着函數的執行結束而結束。實例以下:

var name = "global";

function fun() {
    var name = "小kk";
    console.log(name); // 輸出:小kk

    with ({name:"with"}) {
        console.log(name); // 輸出:with
    }
    console.log(name); // 輸出:小kk
}

fun();

// 不能訪問函數做用域
console.log(name); // 輸出:global

5.沒有塊級做用域

不一樣於其餘編程語言,在JavaScript裏並無塊級做用域,也就是說在for、if、while等語句內部的聲明的變量與在外部聲明是同樣的,在這些語句外部也能夠訪問和修改這些變量的值。實例以下:

function fun() {
    
    if(0 < 2) {
        var name = "小kk";
    }    
    console.log(name); // 輸出:小kk
    name = "change";
    console.log(name); // 輸出:change
}

fun();

6.做用域鏈

JavaScript裏一切皆爲對象,包括函數。函數對象和其它對象同樣,擁有能夠經過代碼訪問的屬性和一系列僅供JavaScript引擎訪問的內部屬性。其中一個內部屬性是做用域,包含了函數被建立的做用域中對象的集合,稱爲函數的做用域鏈,它用來保證對執行環境有權訪問的變量和函數的有序訪問

當一個函數建立後,它的做用域鏈會被建立此函數的做用域中可訪問的數據對象填充。在全局做用域中建立的函數,其做用域鏈會自動成爲全局做用域中的一員。而當函數執行時,其活動對象就會成爲做用域鏈中的第一個對象(活動對象:對象包含了函數的全部局部變量、命名參數、參數集合以及this)。在程序執行時,Javascript引擎會經過搜索上下文的做用域鏈來解析諸如變量和函數名這樣的標識符。其會從做用域鏈的最裏面開始檢索,按照由內到外的順序,直到完成查找,一旦完成查找就結束搜索。若是沒有查詢到標識符聲明,則報錯。當函數執行結束,運行期上下文被銷燬,活動對象也隨之銷燬。實例以下:

var name = 'global';

function fun() {
    console.log(name); // output:global
    name = "change";
    // 函數內部能夠修改全局變量
    console.log(name); // output:change
    // 先查詢活動對象
    var age = "18";
    console.log(age); // output:18
}

fun();

// 函數執行完畢,執行環境銷燬
console.log(age); // output:Uncaught ReferenceError: age is not defined
相關文章
相關標籤/搜索