var let變量提升引發的思考

準備知識

首先我們知道js中有兩種作用域,一是全局作用域,二是函數作用域,然而在es6引入了let之后就有了塊級作用域。也就是說現在有三種作用域了。知道了作用域我們就可以來聊一下變量提升了,一般來說變量提升都是提升到變量作用域第一行。

var的變量提升

var a=1         //var 全局作用域變量
function foo(){
  alert(a) //a是什么?
  var a=2       //var 函數作用域變量
}
foo.call()

做這種題一般先進行轉換(變量提升):先找聲明,再看代碼。

提升結果如下:

找聲明:var a
       function foo(){
         //函數里代碼找聲明時不看,調用時再看
         var a
         alert(a)
         a=2
       }
看代碼:a=1
       foo.call()//調用了,看函數代碼

由上面可以看到a為undefined

let的變量提升

let 有變量提升?答案是有的,聽我慢慢說。

let x = 'global'
{
  console.log(x) // Uncaught ReferenceError: x is not defined
  let x = 1
}

你能解釋一下為什么x會報錯。
能想通嗎?
我們先從提升這兩個字入手理解一下

提升

首先明確一點:提升不是一個技術名詞。
要搞清楚提升的本質,需要理解 JS 變量的「創建create、初始化initialize 和賦值assign」
有的地方把創建說成是聲明(declare),為了將這個概念與變量聲明區別開,我故意不使用聲明這個字眼。
有的地方把初始化叫做綁定(binding),但我感覺這個詞不如初始化形象。

var 聲明的「創建、初始化和賦值」過程

function fn(){ 
  var x = 1 
  var y = 2
}
fn()

在執行 fn 時,會有以下過程(不完全):

  1. 進入 fn,為 fn 創建一個環境。
  2. 找到 fn 中所有用 var 聲明的變量,在這個環境中「創建」這些變量(即 x 和 y)。
  3. 將這些變量「初始化」為 undefined。
  4. 開始執行代碼
    x = 1 將 x 變量「賦值」為 1
    y = 2 將 y 變量「賦值」為 2

也就是說 var 聲明會在代碼執行之前就將「創建變量,并將其初始化為 undefined」。
這就解釋了為什么在 var x = 1 之前 console.log(x) 會得到 undefined。

function 聲明的「創建、初始化和賦值」過程

fn2()
function fn2(){ 
  console.log(2)
}

JS 引擎會有一下過程:
1.找到所有用 function 聲明的變量,在環境中「創建」這些變量。
2.將這些變量「初始化」并「賦值」為 function(){ console.log(2) }。
3.開始執行代碼 fn2()

也就是說 function 聲明會在代碼執行之前就「創建、初始化并賦值」。

let 聲明的「創建、初始化和賦值」過程

{
 let x = 1 
 x = 2
}

我們只看 {} 里面的過程:
1.找到所有用 let 聲明的變量,在環境中「創建」這些變量
2.開始執行代碼(注意現在還沒有初始化)
3.執行 x = 1,將 x 「初始化」為 1(這并不是一次賦值,如果代碼是 let x,就將 x 初始化為 undefined)
3.執行 x = 2,對 x 進行「賦值」

這就解釋了為什么在 之前let x 之前使用 x 會報錯:

let x = 'global'{ 
  console.log(x) // Uncaught ReferenceError: x is not defined 
  let x = 1
}

原因有兩個

  1. console.log(x) 中的 x 指的是下面的 x,而不是全局的 x
  2. 執行 log 時 x 還沒「初始化」,所以不能使用(也就是所謂的暫時死區)

總的來說:

  1. let 的「創建」過程被提升了,但是初始化沒有提升。
  2. var 的「創建」和「初始化」都被提升了。
  3. function 的「創建」「初始化」和「賦值」都被提升了。
    也就是提升的東西一步步變多了。

講了這么多我們順便來總結一下let的特點

let的5個特點

  1. let 塊級作用域變量

  2. let無法重復聲明

  3. let和for循環配合會有神奇現象

    var liList = document.querySelectorAll('li') // 共5個li
    for( let i=0; i<liList.length; i++){
      liList[i].onclick = function(){
        console.log(i)///為何分別打印出 0、1、2、3、4,把let 改成var 就是打印出5個4?
      }
    }
    

    原因

    1. for( let i = 0; i< 5; i++) 這句話的圓括號之間,有一個隱藏的作用域
    2. for( let i = 0; i< 5; i++) { 循環體 } 在每次執行循環體之前,JS 引擎會把 i 在循環體的
      上下文中重新聲明及初始化一次。

    上面代碼等價于

    var liList = document.querySelectorAll('li') // 共5個li
    for( let i=0; i<liList.length; i++){
      let i = 隱藏作用域中的i // 看這里看這里看這里
      liList[i].onclick = function(){
        console.log(i)
      }
    }
    

    這時:let i 保留的隱藏作用域中的 i 的值,所以在后者變化的時候,前者并不會變化。
    而console.log 的是前者,所以不會出現分別打印出 0、1、2、3、4

  4. let 會提升,提升到block(塊級作用域)第一行

  5. 但是有TDZ(臨時死亡區域),let聲明之前區域不能使用。

參考文章:https://zhuanlan.zhihu.com/p/28140450?utm_medium=social&utm_source=qq

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容