對於let 是否存在變量提高的思考?

面試的時候,咱們常常會被問到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

經過這篇文章,以及這篇文章所提到的文章得出的結論就是:

let 的「建立」過程被提高了,可是初始化沒有提高。

接下來就講爲何?從變量的生命週期開始!

一、變量的生命週期

在這裏我比較喜歡這篇文章:JavaScript Variables Lifecycle: Why let Is Not Hoisted

首先咱們來學習一下變量的生命週期:

當引擎使用變量時,它們的生命週期包括如下幾個階段:

  1. 聲明階段(Declaration phase)正在範圍內註冊變量。
  2. 初始化階段(Initialization phase)是分配內存併爲做用域中的變量建立綁定。在此步驟中,變量將使用進行自動初始化undefined。
  3. 分配階段(Assignment phase)是爲初始化變量分配一個值。

變量在經過聲明階段時已處於統一狀態,但還沒有達到初始化狀態。

可是要注意的是,就變量生命週期而言,聲明階段與通常而言的變量聲明是不一樣的術語。簡而言之,引擎在三個階段處理變量聲明:聲明階段,初始化階段和賦值階段。

1.1 var變量的生命週期

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)。

1.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) {...}。

1.3 let變量生命週期

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變量了。

1.3.1 爲何 Hosting 在生命週期中無效

如上所述,提高是變量在頂部的耦合聲明和初始化。 可是let聲明的變量,他的生命週期使聲明和初始化階段脫鉤。解耦消除了 Hosting的術語let。 這兩個階段之間的間隙建立了臨時死區,沒法訪問該變量。

總結

let 的「建立」過程被提高了,可是初始化沒有提高。由於聲明和初始化階段是分離的,因此提高對於let變量(包括for constclass)無效。在初始化以前,該變量位於時間死區中而且不可訪問。

在這裏再提一下:

若是 let x的初始化過程失敗了,那麼 x 變量就將永遠處聲明(Declaration)狀態。 你沒法再次對x進行初始化(初始化只有一次機會,而那次機會你失敗了)。 因爲 x 沒法被初始化,因此x永遠處在暫時死區.

相關文章
相關標籤/搜索