【ES6基礎】let和做用域

ECMAScript是一種由Ecma國際(前身爲歐洲計算機制造商協會)以JavaScript爲基礎制定的一種腳本語言標準。目前,該標準基本上每一年發佈一次新的ES規範標準,目前最新的標準是ECMAScript 2018(ES9),因爲前端開發的應用場景日益複雜,自從費時六年之久ES6(ECMAScript 2015)的出現,增長了不少新的特性,讓JavaScript語言更加標準化和工程化。所以咱們有必要從新學習JavaScript,這樣才能適應前端突飛猛進的發展。javascript

從今天開始,小編將會介紹ES6及之後的相關內容,爲了便於理解和學習,每篇文章儘可能簡短。本篇文章小編將帶着你們一塊兒學習如何使用新的語法let聲明變量。前端

本篇文章閱讀時間預計10分鐘。java

你將會學到如下內容:

  • let基本介紹
  • 做用域介紹
    • 做用域
    • 全局做用域和函數做用域
    • 塊級做用域
  • var和let的區別
  • 重複定義變量的問題
  • 提高概念的問題

let介紹

ES6引入了let,用let聲明變量,解決了JavaScript沒有塊級做用域的問題(注:ES3的catch分句會產生塊做用域)。es6

有其它語言背景的好比JAVA,C#開發者來講,這個概念並不難以理解,反而ES6以前,JavaScript沒有塊級做用域,對於新手而言,使用var聲明變量,會讓JavaScript不易懂和難以調試,用很差,甚至會有內存泄露的可能性。爲何會這樣,主要是沒有清楚做用域的概念,接下來咱們首先了解下什麼是做用域。bash

做用域

做用域簡單的來講,就是一套尋找變量的規則,用於肯定在何處以及如何查找變量。說直白點:這些變量在哪裏?它們存儲在哪裏?編譯器如何找到它們?ES6代碼以前,只有全局做用域或函數做用域。微信

當一個塊或函數嵌套在另外一個函數時,就發生了做用域嵌套。如圖所示,就有三個嵌套做用域:函數

  1. 全局做用域,其中有一個標識符:foo(整個綠色區域)
  2. foo建立的函數做用域,其中有三個標識符:a,bar和b(整個黃色區域)
  3. bar建立的函數做用域,其中有一個標識符:c(藍色區域)

如何在嵌套做用域中尋找變量呢:引擎從當前做用域開始查找變量,若是找不到,就會向上一級繼續查找。當抵達最外層全局做用域時,不管找到仍是沒有找到,查找過程當中都會中止。學習

全局做用域和函數做用域

如何理解全局做用域和函數做用域呢,使用var聲明變量時,若是在函數外聲明,就是全局變量,任何函數均可以進行使用,這就是全局做用域查找。若是在函數內使用var聲明變量,就是函數做用域查找,只能在函數內部進行訪問,外部不能進行訪問,以下段代碼所示:ui

var a = 12; // 全局做用域都能訪問
 function myFunction() {
   console.log(a); // alerts 12
   var b = 13;
   if(true) {
     var c = 14; // 函數內部能夠訪問
     alert(b); // alerts 13
   }
   alert(c); // alerts 14
 }
 myFunction();
 alert(b); // alerts undefined複製代碼

爲何b變量訪問不到?由於變量b是在函數內進行聲明的,所以函數執行完後,因爲垃圾數據回收機制的存在,引擎認爲函數執行完了,變量應該進行銷燬。this

若是你在函數內忘記寫了b標識前忘記寫了var,引擎就會自做聰明,在函數外全局做用域爲你自動聲明變量b,這樣在函數外就能訪問b變量了(全局做用域)。

所以使用var進行聲明時,若是一不當心,你就會聲明一個全局做用域的變量,更糟糕的狀況還有可能污染一個同名的變量,所以產生的BUG就很難查找。

如下這個例子會更加明顯,也是開發者常常會出現的問題,i變量會綁定到外部做用域(函數或全局做用域),污染整個外部做用域:

for(var i=0;i<10;i++){
	console.log(i); //依次輸出1到9
}
console.log(i);//10複製代碼

塊級做用域

幸虧es6引入了let,避免了有var聲明變量的一些問題,讓變量和函數不只能夠屬於所處的做用域,也能夠屬於某個代碼塊(一般是{...}內部),有一點須要強調,在塊級做用域定義的變量,塊級做用域外是沒法訪問的,以下段代碼所示:

let a = 12; // 全局做用域,能夠訪問
function myFunction() {
   console.log(a); // alerts 12
   let b = 13;
   if(true) {
     let c = 14; // this is NOT accessible throughout the function!
     alert(b); // alerts 13
   }
   alert(c); // alerts undefined {}外,所以沒法訪問
 }
 myFunction();
 alert(b); // alerts undefined {}外,所以沒法訪問複製代碼

在for循環體,使用var和let的區別更加明顯,一個是在全局做用域進行查找變量,一個是在塊級做用域查找變量,塊級做用域每一次執行都會產生一個做用域。首先在for循環裏,使用var聲明變量,以下段代碼所示:

for(var i=0;i<5;i++){
	setTimeout(function() {
		console.log(i); 
	}, 1000);
}
// 輸出 5 5 5 5 5複製代碼

因爲JavaScript是單線程,事件循環機制的存在(不太瞭解事件循環機制的,你們能夠查看《JavaScript基礎——你真的清楚JavaScript是什麼嗎?》),主線程執行for循環後,纔會執行SetTimeOut裏的函數,因爲使用var聲明的變量,做用域會綁定for循環的上一層做用域,因爲for循環執行完後,i的變量天然就等於5,所以setTimeOut在執行內部函數時,查找i變量的值,纔會輸出5。如圖所示變量尋找路徑:

將var替換let,將會輸出什麼結果,以下段代碼所示:

for(let i=0;i<5;i++){
	setTimeout(function() {
		console.log(i);
	}, 1000);
}
// 輸出 0,1,2,3,4複製代碼

因爲塊級做用域的存在,每次循環,就會產生一個循環體塊級做用域,所以纔會達到預期的輸出。如圖所示變量尋找路徑:

var和let的比較

對比項

let

var

聲明變量

能夠被釋放

能夠被提高

重複定義檢查

可被用於塊狀做用域

重複定義變量問題

用var在同一個做用域重複定義變量,後者將會覆蓋前者聲明的變量的值,以下段代碼所示:

var a = 0;
var a = 1;
alert(a); // alerts 1
function myFunction() {
 var b = 2;
 var b = 3;
 alert(b); // alerts 3
}
myFunction();複製代碼

使用let在同一做用域下重複定義變量,將會產生SyntaxError的錯誤,以下段代碼所示:

let a = 0;
let a = 1; // SyntaxError
function myFunction() {
 let b = 2;
 let b = 3; // SyntaxError
 if(true) {
	let c = 4;
	let c = 5; // SyntaxError
 }
}
myFunction();複製代碼

若是你在嵌套做用域裏進行從新定義變量,雖然變量名相同,可是不是同一變量,以下段代碼所示:

var a = 1;
let b = 2;
function myFunction() {
	var a = 3; // different variable
	let b = 4; // different variable
	if(true) {
	  var a = 5; // overwritten
	  let b = 6; // different variable
	  console.log(a); // 5
	  console.log(b); // 6
}
	  console.log(a); // 5
	  console.log(b); // 4
}
myFunction();
console.log(a);
console.log(b);複製代碼

提高概念的問題

初學JavaScript的同窗,直覺上會認爲編譯器會由上到下一行行的執行,其實並不正確,函數聲明和變量聲明都會被提高(使用var聲明變量,let聲明變量將不會被提高)。函數首先會被提高,而後纔是變量提高。

首先咱們看下段函數提高的代碼:

bookName("ES8 Concepts");
function bookName(name) {
	console.log("I'm reading " + name);//I'm reading ES8 Concepts
}複製代碼

正常輸出,因爲函數會提高至執行語句前。在來看如下代碼,使用變量的方式聲明函數:

bookName("ES8 Concepts"); //TypeError: bookName is not a function
var bookName = function(name) {
	console.log("I'm reading " + name);
}複製代碼

爲何會這樣呢,JavaScript引擎只會先提高函數,在提高變量聲明,引擎將會對上述代碼這樣調整,代碼以下:

var bookName; // 變量聲明提高至最上面
bookName("ES8 Concepts"); // bookName is not function 
// because bookName is undefined
bookName = function(name) { // 變量賦值不會被提高
	console.log("I'm reading " + name);
}複製代碼

若是使用let替換var聲明函數呢?將會有什麼提示輸出呢?以下段代碼所示:

bookName("ES8 Concepts"); // ReferenceError: bookName is not defined
let bookName = function(name) {
	console.log("I'm reading " + name);
}複製代碼

從中能夠看出,使用let聲明的變量將不會產生變量聲明提高。這樣的好處就是,讓咱們更好的按照由上到下的常規方式書寫代碼,儘可能避免提高問題產生的難以查找的問題。

小節

今天的文章就到這裏,從中咱們能夠看出let能夠說是var的進化版,爲了不產生奇奇怪怪的問題,讓咱們能按照大多數高級語言書寫代碼的思惟方式,在絕大部分狀況下,咱們應該使用let聲明變量,讓咱們的代碼更加易於維護和使用。

本文部份內容參:《你不知道的JavaScript》

更多精彩內容,請微信關注」前端達人」公衆號!

相關文章
相關標籤/搜索