字數:2869javascript
閱讀時間:10分鐘java
最新的ECMAScript規範中,變量聲明有var、function、let、const、import、class六種方法。es6
語法:編程
var varname [= value1 [, vaname1[,valname2 ...]]];瀏覽器
對應以下代碼:安全
var x; var x=1; var x=1,y=2; var x=y,y=2; //x->undefined y->2
咱們須要注意該語法的兩個特性:塊級做用域和變量提高。微信
var語法聲明的變量做用域爲當前上下文(能夠簡化理解爲當前變量被包裹的函數或頂級做用域),這裏要特別注意,代碼塊並無建立新的上下文,因此 var 沒有塊級做用域的概念。咱們在一個循環體或者判斷條件語句中使用的變量都會存儲到當前函數對應的上下文中。例:app
function test () { console.log(i); for (var i = 0; i < 10; i++) { console.log(i); } console.log(i); } test(); console.log(i); //輸出結果:undefined 0 1 2 3 4 5 6 7 8 9 10 ReferenceError
在這個示例中,變量i
是在循環體中聲明的,可是咱們在循環體外仍然能夠訪問到它,而在函數以外就會報引用錯誤。說明,i
的做用域在整個test函數中。異步
咱們再來看看,在test函數中,咱們在變量聲明以前就使用了該變量而且能夠正常運行,這就是變量提高。js解析器在解析js代碼時,會先遍歷該函數中的全部變量聲明,凡是使用var語句聲明的變量,都會將他們的聲明語句提高到函數頂端,即上述代碼正真執行的是以下代碼:編程語言
function test () { var i; //變量提高 console.log(i); for (i = 0; i < 10; i++) { console.log(i); } console.log(i); } test(); console.log(i);
理解以上兩點,就基本上理解了var的使用方式了。至於像i=10
不使用var語句,直接聲明一個變量這種寫法,實質就是在全局對象上添加一個屬性而已。例如,在瀏覽器中,其實等同於window.i = 10;
。看到這樣的代碼咱們明白什麼意思就好了,咱們本身就千萬不要寫這種魔法代碼了。(在代碼頂層經過var語法聲明一個變量,實質也是在全局變量上添加一個屬性)
語法:
function name([param [,param1,...]]){
[statements]
}
函數聲明的效果同 var 語句。須要注意下文 let 介紹中的函數聲明。
MDN對let取名的解釋:
Let是一個數學聲明,是採用於早期的編程語言如Scheme和Basic。 變量被認爲是不適合更高層次抽象的低級實體,所以許多語言設計者但願引入相似但更強大的概念, 如在Clojure、f#、Scala,let可能意味着一個值,或者一個變量能夠賦值,但不能被更改, 這反過來使編譯器可以捕獲更多的編程錯誤和優化代碼更好。 javascript從一開始就有var,因此他們只是須要另外一個關鍵字,並只是借用了其餘數十種語言, 使用let已經做爲一個傳統的儘量接近var的關鍵字,雖然在javascript 中 let只建立塊範圍局部變量而已。
語法:
let letname [ = letval[,letname1 = letval1[,...]]] ;
let語法與var語法使用方式徹底同樣,主要區別在於做用域和變量提高兩個特性的不一樣。
首先,let語法不會進行變量提高,在聲明前使用變量會報引用錯誤。而後,let是塊級做用域,並不是做用於整個封閉函數。舉個栗子:
//變量提高 console.log(valVal); //undefined var valVal = 'valVal'; console.log(letVal); //ReferenceError let letVal = 'letVal'; //做用域 for (let i = 0; i < 10; i++) { console.log(i); } console.log(i); //ReferenceError
①TDZ(temporal dead zone)臨時死鎖域
只要在塊級做用域中聲明瞭let變量,該變量就和當前做用域綁定,再也不受到外面做用域的影響。見以下示例:
{ let testval = 'val'; { let testval = 'val1'; console.log(testval); //val1 } console.log(testval); //val } { var testval = 'val'; { var testval = 'val1'; console.log(testval); //val1 } console.log(testval); //val1 }
如上所示,當js代碼解析到一個代碼塊中時,會先檢測代碼塊中的let語句,而後將檢測到的變量綁定到當前做用域中,全部對該變量的操做都會做用於綁定做用域上的變量,不會受外側做用域的影響。所以,雖然外側做用域聲明瞭testval變量,但代碼解析到內層做用域會從新綁定內層的testval變量,對變量的賦值操做也僅僅做用於當前做用域對應的變量上。所以,內層打印的變量和外層打印的變量會不一致。上例的第二段代碼演示了var語法的效果,它就沒有臨時死鎖域的機制。
所以,咱們在使用以下代碼時須要注意了:
{ let testval = 'val'; { console.log(testval); //ReferenceError let testval = 'val1'; } function testFun (x = y, y = 1) { } testFun(); //y is not defined }
②重複聲明
let語法不容許重複聲明,測試代碼以下:
//重複聲明 { var testval = 'val'; var testval = 'val1'; console.log(testval); //val1 } { let testval = 'val1'; var testval = 'val'; //重複定義錯誤 console.log(testval); } { var testval = 'val'; //重複定義錯誤 let testval = 'val1'; console.log(testval); } { let testval = 'val'; let testval = 'val1'; //重複定義錯誤 console.log(testval); }
var語法支持重複聲明,可是一旦使用let語法,就不容許重複聲明瞭。上例中,雖然,第二個代碼塊和第三個代碼塊中var語句的順序不一致,但都是var語句報錯,這也是上述TDZ機制致使的。
函數參數也是同樣,不容許重複聲明,示例以下:
{ function testFun(arg){ let arg = 'arg'; //重複定義錯誤 } testFun(arg); }
③switch語句
因爲switch語句中,case語句並無建立一個做用域塊,因此也不能重複聲明變量:
//switch { let caseVal = '1'; switch (caseVal) { case '1': let val = '1'; break; case '2': let val = '2'; //重複定義錯誤 break; default: } }
這裏插播一下,判斷時候產生新的塊狀做用域的最簡單的方式就是是否有一對大括號包裹。若是有,就是一個新的塊做用域。
這時,有人就會問了,那若是我寫一個沒有大括號的條件語句會如何呢?看以下示例:
{ let testVal = ''; if(0) let testVal = '1'; //Lexical declaration cannot appear in a single-statement context } { let testVal = ''; if(0) var testVal = '1'; //重複定義錯誤 } { var testVal = ''; if (0) var testVal = '1'; //正確運行 }
那串英文的意思是:詞法聲明不能出如今單個語句的上下文中。簡單來講,就是在單行的條件語句中,不容許使用let表達式。
④typeof
由於TDZ的機制,致使以前絕對安全的 typeof 語句再也不安全。舉個栗子:
console.log(typeof letval); //報變量未定義錯誤 let letval = 1;
因此,咱們在使用變量時,仍是先申明再使用爲好。
⑤函數聲明
ES5中規定,函數只能在頂層做用域或函數做用域中聲明,不能在塊級做用域中聲明。可是爲了兼容ES5以前的代碼,瀏覽器並無遵循這條規範,仍然支持在塊級做用域中聲明函數。
到了ES6中,明確容許在塊級做用域中聲明函數。而且,函數的聲明默認是使用let語法的,即在塊級做用域以外,咱們是沒法訪問到該函數。舉個栗子:
{ function testFun () { console.log('testFun'); } } testFun();
按照規範,理應報函數未定義錯誤。但實際運行結果爲正確輸出了 testFun 。
原來由於這條規則對以前的代碼影響太大,因此ES6在附錄B中規定,瀏覽器的實現能夠不遵循這條規定,能夠有本身的行爲方式。遵循如下兩條:
-容許在塊級做用域中聲明函數
-函數聲明相似var語法,有變量提高機制。
建議:
因爲這是過分的解決方案,最終仍是會向規範靠攏,建議儘可能不要在塊狀做用域中聲明函數。若是必需要這麼作,則使用 let 語句來聲明函數。
塊級做用域比以前的函數做用域更爲規範和便利,因此建議能夠徹底使用 let 替代 var。舉個經典的栗子:
//var用法 for (var i = 0; i < 10; i++) { (function (i) { setTimeout(function () { console.log(i); }, 100); }(i)); } //let用法 for (let i = 0; i < 10; i++) { setTimeout(function () { console.log(i); }, 100); }
以前咱們想在一個循環語句中執行一個異步的函數,只能經過一個匿名函數來人爲製造一個新的做用域。使用 let 語法,它每次循環都是一個新的做用域,實現起來就很是簡單合理了。
再看一個栗子:
//var var strName = 'iTurst'; function testFun () { console.log(strName); //undefined var strName = 'new name'; } testFun(); //let let strName = 'iTurst'; function testFun () { console.log(strName); //變量未定義錯誤 let strName = 'new name'; } testFun();
這種狀況,使用 var 語句的時候,不會報任何錯誤,可是執行結果卻不是咱們預期的結果,這種機制也很是不合理。在 let 語句中,咱們就明確地知道代碼問題在哪了。
語法:
cons name1 = value1 [, name2 = value2 [...]];
const 語法特性與 let 徹底一致,只是 const 必須在聲明時初始化,然後沒法繼續修改其值。
須要注意,若是 const 語句初始化的值是一個對象,對象的屬性咱們仍是能夠作修改的。因此,在這種狀況下咱們能夠將對象作特殊的處理。
語法:
class name [extends] {
//class body
}
class語句特性:
-class 語法申明的變量其實就是一個函數,和ES5中建立一個類獲得的結果基本一致(除了屬性不可枚舉)。
-不作變量提高
-不可重複定義
class TestClass { } console.log(typeof (TestClass)); //function
這個是ES6新引入的模塊機制,能夠在當前模塊中導入其餘模塊導出的任何類型變量,能夠是一個簡單類型,也能夠是一個對象。做用域範圍是當前整個模塊。
有關模塊的內容,我後續會有單獨的文章來詳細講述。
①聲明一個類,咱們使用 class 最爲合適。
②變量聲明咱們酌情使用 let 和 const
③函數聲明使用 function 或者 let funName = function()
。
④模塊導入使用 import。
參考資料:
http://es6.ruanyifeng.com/#docs/let
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/let
歡迎關注個人微信公衆號: