面試的時候,咱們常常會被問到let 、const 和 var 之間的區別,個人回答無疑老是那幾點:面試
變量提高bash
var會進行變量提高,let和const不會進行提高函數
暫存死區學習
由於var會進行變量提高,因此能夠在聲明以前訪問,不會造成暫存死區。ui
let 和const 不會進行變量提高,在聲明以前不能使用,造成暫存死區spa
重複聲明code
var能夠進行重複聲明,可是let和const不能進行重複聲明cdn
塊做用域對象
var不會造成塊做用域,let和const能夠造成塊做用域blog
從新賦值
var和let聲明的變量能夠從新賦值,const不能夠。
若是const 聲明的變量存儲的是引用地址, 是能夠修改這個引用對應的對象的值的,可是這個變量不能被賦予其餘值
每次都是這個樣回答,面試官也沒有說什麼。
但有一次面試的時候,一面面試官問到我,let的所謂的暫時性死區怎麼解釋?let和const到底有沒有變量提高?我沒回答出來,而後面試官和我解釋了一下,而且推薦我看一個大神的這篇文章:zhuanlan.zhihu.com/p/28140450
經過這篇文章,以及這篇文章所提到的文章得出的結論就是:
接下來就講爲何?從變量的生命週期開始!
在這裏我比較喜歡這篇文章:JavaScript Variables Lifecycle: Why let Is Not Hoisted
首先咱們來學習一下變量的生命週期:
當引擎使用變量時,它們的生命週期包括如下幾個階段:
Declaration phase
)正在範圍內註冊變量。Initialization phase
)是分配內存併爲做用域中的變量建立綁定。在此步驟中,變量將使用進行自動初始化undefined。Assignment phase
)是爲初始化變量分配一個值。變量在經過聲明階段時已處於統一狀態,但還沒有達到初始化狀態。
可是要注意的是,就變量生命週期而言,聲明階段與通常而言的變量聲明是不一樣的術語。簡而言之,引擎在三個階段處理變量聲明:聲明階段,初始化階段和賦值階段。
var聲明的變量在所在的做用域的開始處聲明和初始化變量。聲明和初始化階段之間沒有差距。
function var_variable(){
console.log('在聲明階段以前',one_variable);
var one_variable;
console.log('在未賦值以前',one_variable);
one_variable = 'be assigned';
console.log('賦值以後', one_variable);
}
//console.log('在函數做用域外是否能夠訪問到var聲明的變量', one_variable);//one_variable is not defined
var_variable();
複製代碼
運行結果:
在未聲明階段以前 undefined
在未賦值以前 undefined
賦值以後 be assigned
複製代碼
在執行任何語句以前,變量在做用域的開頭經過聲明階段並當即初始化階段(上圖步驟1)。 var variable語句在函數做用域中的位置不影響聲明和初始化階段。
在聲明和初始化以後,可是在賦值階段以前,該變量具備undefined值而且能夠被使用。
在賦值階段 variable = 'value',變量將接收其初始值(上圖步驟2)。
函數聲明:function funName() {...}
function 聲明會在代碼執行以前就「建立、初始化並賦值」。
function sumArray(array) {
return array.reduce(sum);
function sum(a, b) {
return a + b;
}
}
sumArray([5, 10, 8]); // => 23
複製代碼
執行JavaScript時sumArray([5, 10, 8]),它將進入sumArray函數範圍。在此範圍內,緊接着執行任何語句以前,sum將經過全部三個階段:聲明,初始化和賦值。 這種方式甚至array.reduce(sum)能夠sum在其聲明語句以前使用function sum(a, b) {...}。
let變量的處理方式不一樣於var。主要區別是聲明和初始化階段是分開的。
let a = 1;
{
// 初始化前沒法訪問「a」,在這裏的a指的是下面的a,不是全局的a,此時a尚未被初始化,
//因此在這裏log會報錯,由於在這裏是暫時性死區
// console.log(a);
let a;
//解釋器進入包含let variable語句的塊範圍的狀況。變量當即經過聲明階段,在範圍內註冊其名稱,
//而後解釋器繼續逐行解析塊語句。
console.log('########## - a', a);//初始化,對其進行訪問的結果爲undefined
a='被從新賦值了'
console.log('========= - a', a);
console.log('########## - b', b);
var b;
b='b';
console.log('========= - b', b);
}
console.log('全局的a',a);
console.log('全局的b', b);
複製代碼
運行結果:
########## - a undefined
========= - a 被從新賦值了
########## - b undefined
========= - b b
全局的a 1
全局的b b
複製代碼
我認爲:在塊級做用域中,從塊級做用域中的第一行開始,到用let variable
聲明變量這一行以前,這一段區域是let的暫時性死區。
這個暫時性死區就是在上圖的Uninitialized state
階段,若是在這一階段的時候,訪問這個變量就會報錯。
在初始化階段Initialized state
以後就能夠訪問這個let
變量了。
Hosting
在生命週期中無效如上所述,提高是變量在頂部的耦合聲明和初始化。 可是let
聲明的變量,他的生命週期使聲明和初始化階段脫鉤。解耦消除了 Hosting
的術語let
。 這兩個階段之間的間隙建立了臨時死區,沒法訪問該變量。
let
的「建立」過程被提高了,可是初始化沒有提高。由於聲明和初始化階段是分離的,因此提高對於let
變量(包括for const
和class
)無效。在初始化以前,該變量位於時間死區中而且不可訪問。
在這裏再提一下:
若是
let x
的初始化過程失敗了,那麼x
變量就將永遠處聲明(Declaration
)狀態。 你沒法再次對x
進行初始化(初始化只有一次機會,而那次機會你失敗了)。 因爲 x 沒法被初始化,因此x
永遠處在暫時死區.