ES6深入學習(一)塊級作用域詳解

Thea_more發表於2019-04-18

JavaScript 中,函式及變數的宣告都將被提升到函式的最頂部。 注意:只有宣告本身被提升,而任何賦值或者其他執行邏輯都被留在原處。 函式宣告會被提升,但函式表示式不會

foo() //Function foo
function foo(){
    console.log('Function foo')
}

foo2() //TypeError: foo2 is not a function
var foo2 = function() {
    console.log('Function foo2')
}
複製程式碼

函式宣告和變數宣告都會被提升,但(可以擁有多個“重複”宣告的程式碼中出現)是,函式會優先被提升。 考慮這段程式碼:

foo2() //2
var foo2 = function() {
console.log(1)
}
function foo2(){
console.log(2)
}
foo2()//1
複製程式碼

注意var foo2是一個重複的(因此被無視),即使它出現在function foo2()...宣告之前。因為函式宣告是在普通變數之前被提升的。但後續的宣告會覆蓋前一個。

var宣告及變數提升(Hoisting)機制 在函式作用域或全域性作用域中,通過關鍵字var宣告的變數,無論實際上在哪宣告的,都會被當成在當前作用域頂部宣告的變數。

function getValue(flag){
    //var value,變數value的宣告被提升至函式頂部,而初始化操作停留在原處執行
    if(flag){
        var value = "true";  
    }else{
        return null
    }
    //此處可以訪問變數value,值為undefined
}
複製程式碼

塊級宣告

塊級作用域 (也被稱為詞法作用域)存在於函式內部、塊中({}之間的區域)

  • let宣告的用法與var相同,用let代替var來宣告可以把變數的作用域限制在當前程式碼塊中。let宣告不會被提升,假設作用域中已經存在某個識別符號,在使用let關鍵字宣告則會丟擲錯誤。
var a = 1;
let a = 2;//Uncaught SyntaxError

var b = 3;
if(true){
    let b = 4;//不會丟擲錯誤
}
複製程式碼
  • const宣告一個常量,其值一旦被設定後不可更改,因此通過const宣告的常量必須進行初始化。

  • 對於複合型別的變數,const宣告不允許修改繫結,但允許修改值。變數名不指向資料,而是指向資料所在的地址,const命令只是保證變數名指向的地址不變,並不能保證該地址的資料不變,急可以修改該物件的屬性值。

const b = 'b';
b = 'c';//Uncaught SyntaxError: 

const obj = {};
obj.a = 'a';
console.log(obj);//{a:'a'}
複製程式碼

臨時死區(Temporal Dead Zone) 與var不同,let和const宣告的變數不會被提升到作用域頂部,如果在宣告之前訪問這些變數,即使相對安全的typeof操作符也會觸發引用錯誤

if(true){
    console.log(typeof a)//Uncaught ReferenceError
    let a = 1;
}
複製程式碼

由於console.log(typeof a)語句會丟擲錯誤,因此用let定義並初始化變數a的語句不會被執行,此時a還位於所謂的臨時死區(TDZ)中;將let換成const會有相同的效果。 JavaScript引擎在執行程式碼是發現變數宣告時,要麼將它們提升至作用域頂部(var宣告),要麼將宣告放到TDZ中(let和const宣告)。訪問TDZ中的變數會觸發執行是的錯誤,只有執行過變數宣告語句後,變數才會從TDZ中移出,然後方可正常訪問。

迴圈中的塊作用域繫結

for(var i = 0;i<3;i++){
    console.log(i)//0,1,2
}
console.log(i)//3
//由於var宣告提升,變數i在迴圈結束後仍可以訪問。如果換用let宣告,迴圈結束後訪問i則會丟擲一個錯誤
複製程式碼

迴圈中的函式:

var data = [];
for(var i = 0;i<10;i++){
    data[i] = function(){
        console.log(i)
        return i
    }
}
data[1]();//10
複製程式碼

**迴圈裡的每次迭代同時共享著變數i,迴圈內部建立的函式保留了對相同變數的引用,迴圈結束時變數i的值為10,所以每次呼叫console.log(i)時就會輸出是在10。

為解決這個問題,通常會使用立即呼叫函式表示式(IIFE),以強制生成計數器變數的副本

var data = [];
for(var i = 0;i<10;i++){
    data[i] = function(){
        console.log(i);
        return i
    }()
}
data[5];//5
複製程式碼

迴圈中的let宣告 let宣告模仿上述示例中IIFE所做的一切來簡化迴圈過程,每次迭代都會建立一個新變數,並以之前迭代中同名變數的值將其初始化(即刪除IIFE之後仍可得到預期的效果)

var arr = [];
for(let i = 0;i<10;i++) {
    data[i] = function(){
        return i
    }
}
data[1]();//1
複製程式碼

全域性作用域繫結

let和const與var的另一個區別是它們在全域性作用域中的行為,當var被用於全域性作用域時,會建立一個新的全域性變數作為全域性物件(瀏覽器中的window物件)的屬性,這意味著var可能會無意覆蓋一個已經存在的全域性屬性

//瀏覽器中
var RegExp = "hello";
console.log(window.RegExp);//hello
複製程式碼

即使全域性物件RegExp定義在window上,也不能倖免於var宣告覆蓋。如果使用let或const,會在全域性作用一下建立一個新的繫結,但該繫結不會新增為全域性物件的屬性,即不會覆蓋全域性變數,只能遮蔽它。

let RegExp = "hello";
console.log(RegExp)//"hello"
console.log(window.RegExp === RegExp);//false,不會破壞全域性作用域
複製程式碼

相關文章:ES6深入學習(二)關於函式(juejin.im/post/5cb7e9…)

相關文章