概述
Javascript中的範圍和內容相關的實現是Javascript語言專屬的特性,從某種程度上來說,Javascript語言是十分靈活的。Javascript中的函數可以採用各種各樣的上下文,範圍也可以被封裝和儲存。正是由於這些特性,Javascript中也提供了很多很有用的設計模式。然而,範圍和上下文也是Javascript程式員在開發中經常迷惑的地方。
下面會向大家介紹Javascript中範圍和內容相關的概念,以及它們的不同。
範圍 VS 上下文
首先要說明的很重要的一點是範圍和上下文並不是同一個概念,它們指代的並不是同一個東西。作為一個前端的菜逼,經常會看到一些文章把這兩個概念弄混,結果有些東西越看越不明白。這兩個概念貌似被混淆了很長一段時間了。因此,查了很多資料,簡單說明下這兩個概念。:stuck_out_tongue_closed_eyes:
在Javascript中,當一個函數被調用時都會有一個範圍和上下文和這個函數綁定在一起。從根本上來說,範圍是基於函數的而上下文是基於對象的。換句話說,範圍適用於函數被調用時函數中變數的存取權限。上下文通常是指“this”關鍵字的值,“this”是擁有當前執行代碼的對象的引用。
變數範圍
變數可以被定義在局域或全域範圍中,分別稱為局部變數和全域變數。全域變數是指在函數體外聲明的變數,在程式的任何地方都可以訪問全域變數。局部變數是指在函數體內定義的變數,它僅可以在函數體內或者嵌套的函數內被訪問,並且不能在函數外部被訪問。
Javascript目前並不支援塊級範圍(在if、switch、for等語句中定義的變數)。這意味著在塊內定義的變數,在塊外也可以訪問。但是,在ES6中,我們可以使用“let”關鍵字定義塊級範圍。
關於範圍的內容,大家可以查下別的資料,這部分內容相對簡單些。
“this”上下文
上下文(context)通常取決於函數被調用的方式。當函數作為對象的方法被調用時,“this”指代的是調用該函數的對象。
複製代碼 代碼如下:
var obj={
foo:function (){
console.log(this === obj);
}
};
obj.foo(); //輸出true
同樣,當我們使用“new”關鍵字建立新對象時,this引用的是新建立的對象。
複製代碼 代碼如下:
function foo(){
console.log(this);
}
foo(); //輸出window
var obj=new foo(); //輸出 foo {}
有一點需要注意的是,當全域範圍中的函數被被調用時,this引用的是全域對象,在瀏覽器環境中指的就是window。但是,如果在strict 模式下運行代碼時,“this”被設定為“undefined”
執行內容(Execution Context)
Javascript是單線程的語言,這也就是說Javascript在瀏覽器中運行時,一次只能做一件事情,其他的事情將被方法隊列中,等待被處理。
1.當Javascript代碼檔案被瀏覽器載入後,預設最新進入的是一個全域的執行內容。當在全域上下文中調用一個函數時,程式留就進入該被調用函數內,此時Javascript引擎就會為該函數建立一個新的執行內容,並且將其壓入到執行內容堆棧的頂部。瀏覽器總是執行當前在堆棧頂部的上下文,一旦執行完畢,該上下文就會從堆棧頂部被彈出,然後,進入其下的上下文執行代碼。這樣,堆棧中的上下文就會被依次執行並且彈出堆棧,直到回到全域的上下文。
2.一個執行內容可以被分為兩個階段:建立階段和執行階段。在建立階段,javascript解譯器首先會建立一個變數對象(也成為“使用中的物件”,activation object)。使用中的物件由變數,函式宣告和參數組成。在這個階段,函數的範圍鏈被初始化,this引用的對象也被確定。接下來就是執行階段,在這個階段,代碼被解釋並執行。
在Javascript代碼中,可以有任意多個函數上下文,我們已經知道,當函數被調用時,Javascript解譯器就會建立一個新的上下文,同時會建立一個私人的範圍,函數內部聲明的任何變數都不能在當前函數範圍外部直接存取。
3.通過上面的解釋,我們對函數的“執行內容”有了一個基本的概念,但是這裡也是大家最容易迷惑的一個地方。Javascript中的“執行內容”主要是指範圍,而不是上面第四小節中指的“this上下文”。類似的容易混淆的概念在Javascript中還有很多,但是我們只要弄清楚了每個概念所指代的具體對象,就不會再迷惑,因此,這裡也希望大家能夠真正的區分開“執行內容”和“this上下文”。
簡單的一句話概括來說,執行內容是與範圍相關的概念,雖然這樣說可能不太嚴謹。
範圍鏈
對每一個執行內容來說,都有一個範圍連跟它綁定在一起。範圍鏈包含了執行內容堆棧中的執行內容使用中的物件(activation object,聽起來有點繞口)。範圍鏈決定了變數的訪問和標識符的解析。
程式碼範例:
複製代碼 代碼如下:
function first(){
second();
function second(){
third();
function third(){
fourth();
function fourth(){
//代碼
}
}
}
}
first();
執行上面的代碼,嵌套的函數都會被執行。就上面的代碼來說,也會形成一個範圍鏈,範圍鏈從頂部到底部的順序為:fourth, third, second, first, global。函數fourth可以訪問全域範圍中的變數,並且可以訪問函數third, second, first中定義的任何變數。
有一點需要注意的是,在函數體內,局部變數的優先順序高於同名的全域變數。如果在函數內聲明的局部變數或函數參數中帶有的變數和全域變數重名,那麼全域變數就被局部變數所覆蓋。
簡單來說,每次我們嘗試訪問一個變數時,程式都會在當前函數範圍內尋找變數,如果找不到就沿著範圍鏈到該函數的上層去尋找,直到找到該變數為止,如果找不到則返回undefined。
總結
這篇文章介紹了javascript中上下文和範圍的相關概念,javascript中還有幾個比較重要的概念,如閉包等,這些在以後自己弄明白了會寫成文章~~