ES6系列——let和const深入理解

laihuamin發表於2019-03-02

前言

在ES6中多了兩個變數定義的操作符——let和const,在現在專案中,ES6已經是不可獲缺,我打算在掘金上整理一套ES6的系列,會收集常用的知識點,喜歡的可以點個喜歡,關注,或者可以去github點個star

ES5沒有塊級作用域

大家都知道js是沒有塊級作用域的,我們先了解一下塊級作用域。

任何一對花括號中的語句集都屬於一個塊,在這之中定義的所有變數在程式碼塊外都是不可見的

瞭解定義之後,我們?一個用爛了的例子:

for(var i = 0; i < 10; i++) {
      console.log(1);
    }
console.log(i);複製程式碼

上面這個例子,最外面會輸出10。顯而易見,沒有塊級作用域。

ES5可以怎麼建立塊級作用域

  • 立即執行函式

關於這一點我們可以看道面試題就能明白。

var func = [];
for(var i = 0; i < 10; i++) {
  func.push(function(){
    console.log(i)
  });
}
func.forEach((func) => {
  func();
})
//10個10複製程式碼

為什麼會產生這樣的事情呢?因為在迴圈內部這些i都是用同一個詞法作用域的,換言之,這10個i用的都是最後的輸出的i,最後的i也就等於10。
而立即執行函式就不一樣,他用函式作用域代替塊級作用域,強制在迴圈內部建立副本,以便輸出1,2,3,4…

var func = [];
for(var i = 0; i < 10; i++) {
  func.push((function(value){
    return function() {
      console.log(value)
    }
  })(i));
}
func.forEach((func) => {
  func();
})
//會輸出1到9複製程式碼

對立即執行函式有興趣的好可以看看這麼幾篇博文,我在這裡就不用大篇幅贅述,我們簡單過一下下面幾種方法,然後去將我們今天的主角們。
推薦博文
推薦博文

  • try-catch

try-catch這個建立塊級作用域在紅皮書中有提到過,我沒用過,覺得這個知識點了解就可以,也不常用。

try {
   throw `myException`;
}
catch (e) {
   console.log(e);
}
console.log(e);
//第一個輸出myException,第二個輸出e is not defined複製程式碼

ES6中的塊級作用域

在ES6中提出了let和const,我們可以看一下下面這幾個例子,在每次迴圈中,let會建立一個詞法作用域,並與之前迭代中同名變數的值將其初始化。

for(let i = 0; i < 10; i++) {
      console.log(1);
    }
console.log(i);
//報錯i is not defined複製程式碼
const func = [];
for(let i = 0; i < 10; i++) {
  func.push(function(){
    console.log(i)
  });
}
func.forEach((func) => {
  func();
})
//會輸出0到9複製程式碼

這個特性同樣適用於for in

const funcs = [],
  obj = {
    a: `lai`,
    b: `hua`,
    c: `min`
  };
for (let key in obj) {
  funcs.push(() => {
    console.log(key)
  })
}
funcs.forEach((func) => {
  func()
});
//輸出的是a  b  c複製程式碼

不能重複宣告變數

在一個作用域中,已經用var、let、const宣告過某識別符號之後,不能在用let、const宣告變數,不然會丟擲錯誤

var a = 0;
let a = 10;
// 報錯複製程式碼

但是在作用域中巢狀一個作用域就不會,看下面這個例子

var a = 0;
if (true) {
  let a = 10;
}
// 不會報錯複製程式碼

const效果也是一致的,不過const用於定義常量,const還有以下特性

const宣告變數

當你用const宣告變數,不初始化的話,就會發生報錯

const a;
// 報錯複製程式碼

而const的本質是宣告的,不允許修改繫結,但是允許修改值,所以大多數場景,我們都用const來宣告物件,那樣物件的指標不會改變,相對來說安全,看一下下面的例子

const person = {
  name = `laihuamin`
}
person.name = `lai`;
//到這裡不會發生報錯,只會改變值
person = {};
//這裡改變了物件的指標,所以會發生報錯複製程式碼

而const不止能用於物件指標繫結,還能運用在for in的迭代中,因為每次迭代不會修改已有的繫結,而是會建立新的繫結。看下面的例子

const funcs = [],
  obj = {
    a: `lai`,
    b: `hua`,
    c: `min`
  };
for (const key in obj) {
  funcs.push(() => {
    console.log(key)
  })
}
funcs.forEach((func) => {
  func()
});
//輸出a b c複製程式碼

但是在迴圈中就不能用,迴圈會修改已有的繫結,而const定義的常量時不能修改繫結的,所以會報錯。

沒有變數提升

對於ES5的變數提升有一個經典的考題。如下:

var a = 10;
(function () {
  console.log(a);
  var a = 1;
})();
// 這個會輸出undefined複製程式碼

其實這個很好理解,js作用域連是從內向外尋找變數的,那麼函式的作用域中有a這個變數,由於var會發生變數提升,就相當於下面這個過程

var a;
console.log(a);
a = 1;複製程式碼

所以,這個a變數就是undefined。而let和const就不一樣,把var換成let或者const都會報錯。

暫時性死區

我們先來看例子,再來根據例子解析:

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

let和const定義的變數是存在暫時性死區的,而var沒有,我們來了解一下兩個操作符的工作原理:
對於var而言,當進入var變數的作用域時,會立即為他建立儲存空間,並對它進行初始化,賦值為undefined,當函式載入到變數宣告語句時,會根據語句對變數賦值。
而let和const卻不一樣,當進入let變數的作用域時,會立即給他建立儲存空間,但是不會對他進行初始化,所以會丟擲如上錯誤。

而對於typeof操作符來說,結果是一致的,一樣會報錯:

console.log(typeof a);
let a = 10;
//Uncaught SyntaxError: Identifier `a` has already been declared複製程式碼

所以最佳實踐是把宣告的變數全部提到作用域的開頭,這樣既方便管理,又能避免不必要的麻煩

全域性變數繫結

var宣告全域性變數的時候,當使用關鍵詞,那麼就會覆蓋掉window物件上原本擁有的屬性,我們看一下下面這個例子:

var RegExp = `lai`;
console.log(window.RegExp);
var a = `hua`;
console.log(window.a);
var Array = `min`;
console.log(window.Array);
var b = new Array();
//lai
//hua
//min
//Uncaught TypeError: Array is not a constructor複製程式碼

而換成let和const的時候就不會發生這樣的事情,我們用同樣的例子來看一看:

let RegExp = `lai`;
console.log(window.RegExp);
let a = `hua`;
console.log(window.a);
let Array = `min`;
console.log(window.Array);
let b = new window.Array();
console.log(b);
//ƒ RegExp() { [native code] }
//undefined
//ƒ Array() { [native code] }
//[]複製程式碼

結果和上面一樣,我們更可以進一步認證

let RegExp = `lai`;
console.log(RegExp === window.RegExp);
var Array = `hua`;
console.log(Array === window.Array);

//會輸出 false 和 true複製程式碼

總結

根據以上講的,最佳實踐應該是,能用const定義物件的,不要用let,能用let定義變數的,不要用var。至於他的很多特性,瞭解了能更好的幫助你運用。如果覺得筆者寫的可以的點一個喜歡,之後還會持續更新其他板塊,希望能給筆者的github點個star,謝謝支援

相關文章