js作用域的相關知識
眾所周知,在ES6之前,JavaScript是沒有塊級作用域的,如下圖所示:
學過其他語言的同學肯定有點詫異,為什麽會這樣呢?因為js還是不同於其他語言的,在ES5中,只有全局作用域和函數作用域,並沒有塊作用域,當然我們可以實現塊作用域的功能。看下面代碼:
在這段段代碼中,我們使用立即執行函數(IIFE)創建了一個局部函數來模仿塊級作用域。在ES5時代,JavaScript的作用域只有用全局作用域和局部作用域的說法。到了ES6時代,塊級作用域的登場。
一、關於ES5時代
1.變量提升
說到js的變量提升,就不得不說一下js的詞法分析。總所周知js代碼自上而下執行,但是在js代碼執行前,會先進行詞法分析。所以js運行要分為詞法分析
js詞法分析主要分為3個步驟:
1.分析形參:如果函數有形參,則給當前活動對象增加形參屬性,默認為undefined。
2.分析變量聲明:如果有類似var a 之類的聲明,若沒有該屬性則增加屬性,若已存在則不做操作。默認為undefined。變量的賦值在執行階段才進行,即執行到該變量的時候才有 a = 11。
3.分析函數聲明:類似 function a(){},若當前活動對象沒有該屬性則新增否則重寫該屬性為方法a。
如圖所示,在這段代碼中,按照一般的邏輯,第一個console.log會報錯為“a is not defined”。
但是事實上,根據js詞法分析的第二步,var a這個聲明會被提前到代碼的頂部。但是a=1這個賦值卻不會,所以這段函數正確的步驟為:
這就是所謂的變量提升。
2.函數提升
在js中,我們常見的常見函數的創建方式有三種——函數構造式(不推薦使用,此處不做分析),函數聲明式和函數表達式。下面第一行的代碼為函數聲明式,第二個為函數表達式。
1 function fn1(){}
2 var fn2=function(){};
在以上兩種創建方式中,函數表達式的常見方式與普通變量var a=1的創建方式相同,因此它也會受變量提升的影響。而另一種,函數聲明式會存在函數提升的情況,並且函數提升比變量提升優先級高!因此分別在創建前打印上述fn1和fn2得到以下結果:
根據變量提升和函數提升的分析得:
因此,fn1是作為函數聲明被提升到最前面,而fn2先被作為變量創建並提到頂部,然後在相應位置被賦值的。
3.作用域鏈
我們在上面說到js在ES5時代沒有塊級作用域,只有局部作用域和全局作用域。作用域鏈用於保證對執行環境有權訪問的所有變量和函數的有序訪問。看下圖,按照一般的思路,b輸出為3。這裏就用到了作用域鏈的知識。
當函數在執行的過程中,先從自己內部找變量。如果找不到,再從創建當前函數所在的作用域去找, 以此往上。因此我們分析調整函數得到下圖。在這個函數中給fn賦值為fn1(),即fn1的返回值fn2。在運行fn時,即運行fn2。此時fn2的內部是沒用b的,因此我們要去fn2的創建環境中找b=2。所以此處輸出為2.
二、ES6時代
1.let和const的來臨
首先let
和 const的作用和var是相同的,但是
都是不存在提升,聲明的都是塊級標識符。大括號內部即形成塊級作用域,此時let聲明的a在塊級作用域外是訪問調用不到的
1 { 2 let a=1; 3 } 4 console.log(a)//報錯
並且let和const都禁止重復聲明的:
1 var a = 30; 2 var message = 2; 3 // 這兩條都會拋出語法錯誤 4 let a = 40; 5 const message = 1;每個
const
聲明的常量必須進行初始化,const
定義的常量不能修改,但是用const聲明的對象可以修改值,即
1 const a; // 語法錯誤:常量未初始化 2 const b = { 3 name: ‘a‘ 4 }; 5 b.name = ‘b‘; // 可以修改 6 7 // SyntaxError: "person" is read-only 8 b = { 9 name: ‘c 10 }
let
和const
聲明不會像var
一樣提升到作用域頂部,如果在聲明之前訪問這些變量,會形成所謂的臨時死區(Temporal Dead Zone)即使是相對安全的typeof
操作符也會觸發引用錯誤。用let
來舉例(const也一樣):
1 console.log(typeof value); 2 let value = 1;在上面的代碼中,let無變量提升的作用,即在let value=1之前的代碼出現臨時死區(Temporal Dead Zone),即報錯value is not defined而不是undefined。
1 console.log(typeof a); 2 if(1){ 3 let a=1; 4 }
而在上述的代碼中,let在塊級作用域中,因此在全局作用域中不存在所謂的死區,因此此處打印出undefined。
2.全局塊作用域綁定
在全局作用域中,var 聲明的變量會成為全局對象(瀏覽器環境中的window)的屬性。這意味著var很可能會無意中覆蓋一個已經存在的全局變量。1 var Test=1; 2 window.Test === Test; // true如果在全局作用域中使用let或const,會在全局作用域下創建一個新的綁定,但該綁定不會添加為全局對象的屬性。換句話說,用
let
或const
不能覆蓋全局變量,而只能遮蔽它。
1 const foo = 1; 2 window.foo = 2; 3 console.log(foo); // 1 4 console.log(window.foo); // 2
在實際開發中,let
實際上與我們所用的的var的用法是一樣的
,直接替換符合邏輯。對於需要些保護的變量,我們要使用const。默認使用const,只有確實需要改變變量的值時使用let
。
js作用域的相關知識