變量(變量作用於又稱上下文)和函數生效(能被訪問)的區域或集合。換句話說,作用域決定了代碼區塊中變量和其他資源的可見性。我們來看個例子:
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作用域、作用域鏈基本上就介紹完了,後續我會給大家介紹一下前面說到的 閉包 和 預編譯 。如果大家還有其他不同的理解或者是建議,還請在下發留言。如果有理解的不正確的地方還請多多指教。