ES6之—讀懂let才能少踩坑

Aemple發表於2019-03-01
ES6之—讀懂let才能少踩坑

開聊吧!(前面一小部分大佬們可以自動忽略)

首先,打臉我的無知!

在剛接觸ES6的時候,我就「以為」我理解了let。然後漫長的自學道路上,let一次又一次的讓我認識到了自己的無知。

希望寫了這篇文章之後能用我的無知,讓在這條道路上的人能少踩些坑。


初識let

和很多人一樣,在聽說了ES6很好用之後,就馬不停蹄的去學習這門充滿著語法糖的東西。開始抱著emmmm快速把es6過一遍的念頭去了菜鳥教程(因為覺得這裡的教程很簡潔,但是希望大家以後慎重選擇初學教程!!!)

ES6之—讀懂let才能少踩坑

如圖所示,這就是我對他的初始理解—一個解決javascript沒有塊級作用域的東西。嗯….就是這樣也僅此而已。

let淺解

《1》let 宣告的變數的作用域是塊級的;

{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1
複製程式碼

用法類似於var,但是所宣告的變數,只在let命令所在的程式碼塊內有效.

說說這個「塊級作用域」裡面的坑吧!!!

首先,用阮大神的一個示例來說說過第一個坑

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

輸出幾呢?是6嗎? 答案是10

為什麼呢?因為上面程式碼中,變數i是var命令宣告的,在全域性範圍內都有效,所以全域性只有一個變數i。每一次迴圈,變數i的值都會發生改變,而迴圈內被賦給陣列a的函式內部的console.log(i),裡面的i指向的就是全域性的i。也就是說,所有陣列a的成員裡面的i,指向的都是同一個i,導致執行時輸出的是最後一輪的i的值,也就是 10。

那麼怎麼解決呢?
ES6之前的解決方式–構造閉包形成不銷燬的作用域儲存變數

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

ES6的解決方式

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

程式碼中,變數i是let宣告的,當前的i只在本輪迴圈有效,所以每一次迴圈的i其實都是一個新的變數,所以最後輸出的是6。
用ES5的程式碼描述就是這樣的!

"use strict";

var a = [];

var _loop = function _loop(i) {
  a[i] = function () {
    console.log(i);
  };
};

for (var i = 0; i < 10; i++) {
  _loop(i);
}
a[6](); // 6
複製程式碼

再來一個平常碼程式碼經常會遇到的,面試也會經常遇到。

var liList = document.querySelectorAll(`li`) // 共5個li
for( var i=0; i<liList.length; i++){
  liList[i].onclick = function(){
    console.log(i)
  }
}
複製程式碼

列印多少呢?0,1,2,3,4 還是 5,5,5,5,5?
相信大家大家應該都知道依次點選 li 會列印出 5 個 5。

如果把 var i 改成 let i,就會分別列印出 0、1、2、3、4:

var liList = document.querySelectorAll(`li`) // 共5個li
for( let i=0; i<liList.length; i++){
  liList[i].onclick = function(){
    console.log(i)
  }
}
複製程式碼

闡述一下我的理解吧!

  • for( let i = 0; i< 5; i++) 這句話的圓括號之間,有一個隱藏的作用域。
  • for( let i = 0; i< 5; i++) { 迴圈體 } 在每次執行迴圈體之前,JS 引擎會把 i 在迴圈體的上下文中重新宣告及初始化一次。
  • 用ES5的程式碼描述就是這樣的!
`use strict`;

var liList = document.querySelectorAll(`li`); // 共5個li

var _loop = function _loop(i) {
  liList[i].onclick = function () {
    console.log(i);
  };
};

for (var i = 0; i < liList.length; i++) {
  _loop(i);
}
複製程式碼

總結:希望大家在已有遇到跟迴圈 非同步事件之類的問題的時候多注意下,不要掉坑裡了☺

《2》let 宣告的變數不存在變數提升,有暫時性死區;

先來解釋解釋這兩個吧

變數提升

console.log(foo); // 輸出undefined
console.log(bar); // 報錯ReferenceError

var foo = 2;
let bar = 2;
複製程式碼

let不像var那樣會發生“變數提升”現象。所以,變數一定要在宣告後使用,否則報錯。

暫時性死區

let x = `global`
{
  console.log(x) // Uncaught ReferenceError: x is not defined
  let x = 1
}
複製程式碼

存在全域性變數x,但是塊級作用域內let又宣告瞭一個區域性變數x,導致後者繫結這個塊級作用域,所以在let宣告變數前,對tmp賦值會報錯。

在詳細的解釋這個問題之前,我們先來弄懂ES6之前,我們的作用域裡的變數預解釋。

var a = 1;
function b(params) {
    var c = 2; 
    console.log(params);
}

console.log(a);
b(3);
複製程式碼
ES6之—讀懂let才能少踩坑

紅色框框中的部分是在b函式被呼叫的時候才會按照預解釋規則一步步產生,現在寫出來是為了方便你們理解。正因為b函式被預先宣告且賦值了,所以在b函式之前呼叫他就能得到結果,程式碼如下,相信你又學到不少吧!

b(3);
function b(params) {
    var c = 2; 
    console.log(params);
}

複製程式碼

然後就是函式執行的第二步了

ES6之—讀懂let才能少踩坑

夠清楚!!!!夠明瞭吧!!!

這個我們弄清楚了 就再把let的說一遍 那麼上面的問題你自然就豁然開朗了。

關於let的是這樣的

<1>找到所有用 let 宣告的變數,在環境中「建立」這些變數

<2>開始執行程式碼(注意現在還沒有初始化也沒有假裝賦值為undefined)

<3>執行 let宣告的變數時,將 該變數 「初始化直接賦值」

現在關於上面的不存在變數提升,有暫時性死區你應該都懂了吧。關於暫時性死區是因為let宣告的變數有塊級作用域而且沒有變數提升所以就產生了

也來看看暫時性死區在es6之前的環境裡是怎樣的吧。

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

//之前版本的環境中
`use strict`;

var x = `global`;
{
  console.log(_x); // Uncaught ReferenceError: x is not defined
  var _x = 1;
}

複製程式碼

《3》let 不能重複宣告已存在的變數

這個知識點就基本沒啥坑了 自己注意點兒就好

// 報錯
function () {
  let a = 10;
  var a = 1;
}

// 報錯
function () {
  let a = 10;
  let a = 1;
}
複製程式碼

總結

總共也就三個知識點:完全掌握還是要多用!!!

1、let的塊級作用域!

2、let宣告的變數不存在變數提升,有暫時性死區!

3、let宣告的變數不允許重複宣告,無論重複用var或者其他宣告都不行!

let的故事就這樣暫時說完啦(瘋狂求有位大佬來帶帶我啊) 不知不覺又晚上10點多了 該回寢室了 哈哈 如果學到了就給我個小心心吧 哈哈哈

ES6之—讀懂let才能少踩坑

相關文章