ES6標準入門之---let與const

你的聲先生發表於2019-04-18

ES6標準入門之---let與const

let 與 const,為年輕時犯下的“錯誤”買單!

在講解es6之前,我們必須要提一下es5中的var,也就是曾經的那個錯誤。

if (condition) {
    var value = 1;
}
console.log(value);
複製程式碼

很簡單的分析一下,初學者可能會認為,只有在condition為true的時候,value才會被賦值,如果condition為false的時候,程式碼應該會報錯才對!但是,事實並不是這樣的,瀏覽器在執行這段程式碼的時候,並不是這麼解析的!


var value;
if (condition) {
    value = 1;
}
console.log(value);
複製程式碼

這樣,很容易我們就能判斷出,如果condition的值為false的時候,console出來的值為undefined。

原因就是我們常說的,變數提升

為了加強對變數生命週期的控制,ECMAScript 6 引入了塊級作用域。

塊級作用域存在於:

  • 函式內部

  • 塊中(字元 { 和 } 之間的區域)

引出我們今天的主角—let和const

let和const的特點:

1.不會被提升(真的是這樣的嗎?)

if (condition) {
    let value = 1;
}
console.log(value); // Uncaught ReferenceError: value is not defined
複製程式碼

在程式碼塊外面訪問,直接判定,未定義。

2.重複宣告報錯

var value = 1;
let value = 2; // Uncaught SyntaxError: Identifier 'value' has already been declared
複製程式碼

這在以前是完全可以的,後定義的回覆蓋以前的。

3.不繫結全域性作用域

let value = 1 ;
console.log(window.value); 
複製程式碼

const也是相同的,都訪問不到。

const和let的區別:

const 用於宣告常量,其值一旦被設定不能再被修改,否則會報錯。

值得一提的是:const 宣告不允許修改繫結,但允許修改值。這意味著當用 const 宣告物件時:


const data = {
    value: 1
}

// 沒有問題
data.value = 2;
data.num = 3;

// 報錯
data = {}; // Uncaught TypeError: Assignment to constant variable.
複製程式碼

臨時死區

臨時死區(Temporal Dead Zone),簡寫為 TDZ。

let 和 const 宣告的變數不會被提升到作用域頂部,如果在宣告之前訪問這些變數,會導致報錯。


console.log(typeof value); // Uncaught ReferenceError: value is not defined
let value = 1;
複製程式碼

來個例子~

var value = "global";
// 例子1
(function() {
    console.log(value);
    let value = 'local';
}());

// 例子2
{
    console.log(value);
    const value = 'local';
};
複製程式碼

結果是:都報錯了~!

這是因為 JavaScript 引擎在掃描程式碼發現變數宣告時,要麼將它們提升到作用域頂部(遇到 var 宣告),要麼將宣告放在 TDZ 中(遇到 let 和 const 宣告)。訪問 TDZ 中的變數會觸發執行時錯誤。只有執行過變數宣告語句後,變數才會從 TDZ 中移出,然後方可訪問。 迴圈中的塊級作用域:

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

如何改變現狀呢?我要的是funcs0 == 0

在沒有es6 之前,這個事兒麻煩了,還得使用閉包的方式!

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

ES6 的 let 為這個問題提供了新的解決方法:

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

問題在於,上面講了 let 不提升,不能重複宣告,不能繫結全域性作用域等等特性,可是為什麼在這裡就能正確列印出 i 值呢? 如果是不重複宣告,在迴圈第二次的時候,又用 let宣告瞭i,應該報錯呀,就算因為某種原因,重複宣告不報錯,一遍一遍迭代,i 的值最終還是應該是 3 呀,還有人說 for 迴圈的 設定迴圈變數的那部分是一個單獨的作用域,就比如:


for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc
複製程式碼

這個例子是對的,如果我們把 let 改成 var 呢?

for (var i = 0; i < 3; i++) {
  var i = 'abc';
  console.log(i);
}
// abc
複製程式碼

經查, for 迴圈中使用 let 和 var,底層會使用不同的處理方式。 簡單的來說,就是在 for (let i = 0; i < 3; i++) 中,即圓括號之內建立一個隱藏的作用域,這就可以解釋為什麼:

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc
複製程式碼

然後每次迭代迴圈時都建立一個新變數,並以之前迭代中同名變數的值將其初始化。

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

相當於:

// 虛擬碼
(let i = 0) {
    funcs[0] = function() {
        console.log(i)
    };
}

(let i = 1) {
    funcs[1] = function() {
        console.log(i)
    };
}

(let i = 2) {
    funcs[2] = function() {
        console.log(i)
    };
};

複製程式碼

到此,我們就講完了嗎?沒有,並沒有,上面還有個提升的問題麼!

首先明確一點:提升不是一個技術名詞。

要搞清楚提升的本質,需要理解 JS 變數的 這就解釋了為什麼在 let x 之前使用 x 會報錯:

假設有如下程式碼:

function fn(){
  var x = 1
  var y = 2
}
fn()
複製程式碼

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

  • 進入 fn,為 fn 建立一個環境。

  • 找到 fn 中所有用 var 宣告的變數,在這個環境中「建立」這些變數(即 x 和 y)。

  • 將這些變數「初始化」為 undefined。

  • 開始執行程式碼

  • 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 引擎會有一下過程:

  • 找到所有用 function 宣告的變數,在環境中「建立」這些變數。

  • 將這些變數「初始化」並「賦值」為 function(){ console.log(2) }。

  • 開始執行程式碼 fn2()

也就是說 function 宣告會在程式碼執行之前就「建立、初始化並賦值」

接下來看 let 宣告的「建立、初始化和賦值」過程


{
  let x = 1
  x = 2
}
複製程式碼

我們只看 {} 裡面的過程:

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

  • 開始執行程式碼(注意現在還沒有初始化)

  • 執行 x = 1,將 x 「初始化」為 1(這並不是一次賦值,如果程式碼是 let x,就將 x 初始化為 undefined)

  • 執行 x = 2,對 x 進行「賦值」

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

這就解釋了為什麼在 let x 之前使用 x 會報錯: 原因有兩個:

  • console.log(x) 中的 x 指的是下面的 x,而不是全域性的 x

  • 執行 log 時 x 還沒「初始化」,所以不能使用(也就是所謂的暫時死區)

看到這裡,你應該明白了 let 到底有沒有提升:

  • let 的「建立」過程被提升了,但是初始化沒有提升。

  • var 的「建立」和「初始化」都被提升了。

  • function 的「建立」「初始化」和「賦值」都被提升了。

最後看 const,其實 const 和 let 只有一個區別,那就是 const 只有「建立」和「初始化」,沒有「賦值」過程!!!

這四種宣告,用下圖就可以快速理解:

ES6標準入門之---let與const

所謂暫時死區,就是不能在初始化之前,使用變數。

感謝:zhuanlan.zhihu.com/p/28140450 juejin.im/post/5b0238…

相關文章