深入理解JavaScript作用域、作用域鏈

2020-02-25     sandag

變量(變量作用於又稱上下文)和函數生效(能被訪問)的區域或集合。換句話說,作用域決定了代碼區塊中變量和其他資源的可見性。我們來看個例子:

function myFunction() {        let inVariable = "函數內部變量";    }    myFunction();//要先執行這個函數,否則根本不知道裡面是啥    console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined複製代碼

從上面的例子可以看出作用域的概念,我們創建了一個函數myFunction,在函數內部創建了一個變量inVariable,當我們在全局訪問這個變量沒,系統會直接報錯。這就說明我們在全局是無法獲取到(閉包除外)函數內部的變量。我們可以這樣理解: 作用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。也就是說作用域最大的用處就是隔離變量,不同作用域下同名變量不會有衝突。

2、全局作用域和函數作用域

在代碼中任何地方都能訪問到的對象擁有全局作用域,一般來說以下幾種情形擁有全局作用域

  • 最外層函數和在最外層函數外面定義的變量擁有全局作用域。
let outVariable = "我是外部變量";    function outFunction() {        let inVariable = "我是內部變量";        function inFunction() { // 我是內部函數           console.log(inVariable);         }        inFunction();     }    console.log(outVariable); // 我是外部變量    outFunction(); // 我是內部變量    console.log(inVariable); //inVariable is not defined    inFunction(); //inFunction is not defined
  • 所有末定義直接賦值的變量自動聲明為擁有全局作用域
function outFunction() {        variable = "未定義直接賦值的變量";        var inVariable = "我是內部變量";    }    outFunction();//要先執行這個函數,否則根本不知道裡面是啥    console.log(variable); //未定義直接賦值的變量,這裡會有個「變量提升」    console.log(inVariable); //inVariable is not defined

上面的代碼中在函數outFunction內部variable未定義就先賦值,這裡會將variable變量提升到全局作用域。 變量提升 是發生在函數的預編譯階段,它的意思就是說即任何變量,如果未經聲明就賦值,此變量就為全局對象所有。 變量提升 也叫 暗示全局變量

  • 所有 window 對象的屬性擁有全局作用域一般情況下,window 對象的內置屬性都擁有全局作用域,例如 window.name、window.location
  • 函數作用域聲明在函數內部的變量或方法,它所處的作用域就是函數作用域。在函數外部是無法訪問的(必報除外)。function doSomething(){ var blogName="浪里行舟"; function innerSay(){ alert(blogName); } innerSay(); } alert(blogName); //VM1210:8 Uncaught ReferenceError: blogName is not defined innerSay(); //VM1210:8 Uncaught ReferenceError: innerSay is not defined 複製代碼可見上述代碼中在函數內部聲明的變量或函數,在函數外部是無法訪問的,這說明在函數內部定義的變量或者方法只是函數作用域。

2、什麼是作用域鏈?

在講作用域鏈之前我們首先要知道一個概念[[scope]]

  • [[scope]]我們要知道JavaScript中的每個函數都是一個對象,對象中有些屬性我們能訪問,有些屬性是不可以訪問的,這些屬性僅JavaScript引擎存取。[[scope]]就是其中一個。 。[[scope]]指的就是我們所說的作用域,其中存儲了 運行期上下文 的集合。
  • 運行期上下文當函數執行時,會創建一個稱為執行期上下文的內部對象。一個執行期上下文定義了一個函數執行時的環境,函數每次執行時對應的執行上下文都是獨一無二的,所以多次調用一個函數會導致創建多個執行上下文,當函數執行完畢,執行上下文被銷毀。
  • 作用域鏈[[scope]]中所存儲的執行期上下文對象的集合,這個集合呈鏈式連結,我們把這種鏈式連結叫做作用域鏈。

二、作用域精講

下面我們通過一段代碼詳細介紹一下作用域

function a() {        function b() {            var b = 234;        }        var a = 123;        b();    }    var gloab = 100;    a();    console.log(gloab);    console.log(b);    console.log(a)

我們都知道JavaScript代碼在執行前都會有一個過程叫做 預編譯 ,前面我們提到過。接下來我們一步一步分析。

第一步:a 函數定義

我們可以從上圖中看到,a 函數在被定義時,a函數對象的屬性[[scope]]作用域指向他的作用域鏈scope chain,此時它的作用域鏈的第一項指向了GO(Global Object)全局對象,我們看到全局對象上此時有5個屬性,分別是this、window、document、a、glob。

第二步:a 函數執行

當a函數被執行時,此時a函數對象的作用域[[scope]]的作用域鏈scope chain的第一項指向了AO(Activation Object)活動對象,AO對象里有4個屬性,分別是this、arguments、a、b。第二項指向了GO(Global Object),GO對象里依然有5個屬性,分別是this、window、document、a、golb。

第三步:b 函數定義

當b函數被定義時,此時b函數對象的作用域[[scope]]的作用域鏈scope chain的第一項指向了AO(Activation Object)活動對象,AO對象里有4個屬性,分別是this、arguments、a、b。第二項指向了GO(Global Object),GO對象里依然有5個屬性,分別是this、window、document、a、golb。

第四步:b 函數執行

當b函數被執行時,此時b函數對象的作用域[[scope]]的作用域鏈scope chain的第一項指向了AO(Activation Object)活動對象,AO對象里有3個屬性,分別是this、arguments、b。第一項指向了AO(Activation Object)活動對象,AO對象里有4個屬性,分別是this、arguments、a、b。第二項指向了GO(Global Object),GO對象里依然有5個屬性,分別是this、window、document、a、golb。

以上就是上面代碼執行完之後的結果。當我們訪問其中的變量時,要從作用域鏈的底部向上查找。

首先查找變量glob,作用域鏈第一項上沒有找到繼續在第二項查找,依然沒有,第三項在GO中查找到了,最終的值是100;

其次查找變量b,作用域鏈的第一項上查找,發現存在變量b,最終變量的b的值是234;

最後查找變量a,作用域鏈第一項上沒有找到繼續在第二項查找,發現存在變量a,最終變量a的值是123。

總結

至此JavaScript作用域、作用域鏈基本上就介紹完了,後續我會給大家介紹一下前面說到的 閉包預編譯 。如果大家還有其他不同的理解或者是建議,還請在下發留言。如果有理解的不正確的地方還請多多指教。

文章來源: https://twgreatdaily.com/zh-cn/vvImfHABgx9BqZZIaZvI.html