Javascript基礎之-var,let和const深入解析(二)
你想在在變數宣告之前就使用變數?以後再也別這樣做了。
新的宣告方式(let,const)較之之前的宣告方式(var),還有一個區別,就是新的方式不允許在變數宣告之前就使用該變數,但是var是可以得。請看下面的程式碼,下面這個程式碼是可以正常執行的:
function func() { console.log(localVariable);// undefined var localVariable = 5; console.log(localVariable);// 5 } func();
但是這種卻不可以
function func() { console.log(localVariable); // ReferenceError: localVariable is not defined let localVariable = 10; console.log(localVariable); // 10 } func();
等下,我們上一章曾經介紹了一個叫“提升”的概念,它會吧所有的變數定義在作用域的最前面。這是否意味著如果我不在實際的定義之前使用變數,然後就不會有提升了呢?答案是否定的。提升依然會有,並且適用於所有型別的變數型別。但是const和let卻不是這樣的。
首先,我們看一下var關鍵字是怎麼工作的。規範對其是這樣進行的描述的。
var宣告定義了在正在執行的執行上下文(running execution
context)作用域內的變數環境(VariableEnvironment中)的變數。var變數在當包含的詞法環境(Lexical
Environment)初始化時被建立,在建立的時候被賦值為undefined。[...]
在執行VariableDeclaration時,由帶有Initializer的VariableDeclaration定義的變數被賦其設定項的Initializer's
AssignmentExpression的值。
規範中有許多的細節,讓我們簡單的來看一下:
當你進入到一個作用域中,在內部被定義的所有的變數都會被建立。
所有存在的變數,都可以被訪問,並且會把undefined賦值給該變數。
當代碼(執行時)到達初始化時,會被分配給一個實際的值。
我們來看一下規範中對let和const的表述:
let和const宣告是定義在當前執行上下文作用域中的詞法環境中的變數。當包含的詞法環境被初始化的時候,變數被建立。但是在變數的詞法繫結時被計算之前是不允許通過任何方式來訪問的。當詞法繫結計算時而不是在變數被建立的時候,由詞法繫結定義的變數的初始值被被賦予賦值表示式的值(也就是“=”右邊的表示式)。當詞法繫結被計算的時候,如果let宣告中沒有初始化的值的時候(也就是“let
簡單來說:
如果你進入到了指定的作用域中,它裡面定義的所有的變數都會被初始化,這一點和var很像。
這裡有一個不同點:像var一樣,所有的變數都會存在,但是他們目前還不能被訪問(裡面沒有值,甚至是undefined)。
如果let變數在相同的地方被定義和初始化,他們會被賦予合適的值,反之,變數就是undefined。const變數必須在定義的時候初始化。
我們來看一些相關的例子。
臨時死區
實際上,這種描述引出了我們的另一個定義。他很讓人可怕,因為他叫:臨時死區(TDZ)。這個屬於明確了一個我們無法訪問我們的變數的程式碼的區域。我們來看一下下面的程式碼和相關聯的註釋,來簡單的解釋一下TDZ是什麼。
function func() { // Start of TDZ for deadVariable // we can still do something here, just our deadVariable is not available yet const exampleVariable = 5; console.log(exampleVariable); // 5 // End of TDZ for deadVariable let deadVariable = 10; console.log(deadVariable);// 10 } func();
有一件事情值得去提醒。就是對於名字的建議,這是一個臨時死區,意思這個區域是由時間定義的,而不是位置。因此當執行程式碼的時候,你的宣告在被JS解析器解析之前是不能被訪問的。因此你把使用的變數的位置放在哪裡並不重要,只要是在宣告執行後訪問該變數就可以。所以看下面的程式碼:
function func() { return deadOrAlive; } let deadOrAlive = 'alive!' console.log(func());// alive!
這是執行程式碼的步驟:
函式被宣告
變數deadOrAlive被宣告,並且初始化了一個值“alive”
現在我們呼叫我們的函式。
由於變數deadOrAlive已經被宣告,是可訪問的,因此會打印出正確的結果 “alive”。
但是下面的例子卻會報錯,思考一下原因。
function func() { return deadOrAlive; } console.log(func());// ReferenceError: deadOrAlive is not defined let deadOrAlive = 'dead!'
所以TDZ是一個避免因先使用後宣告而導致的一些詭異的bug而出現的一個很好機制(具體看“提升”相關內容)。我們不需要去額外做什麼事情,就是記住永遠不要在變數宣告之前使用這個變數。即使我們這樣做了,我們也會得到一個很好的報錯資訊。只有一個條件-你必須使用let或者是const來替換掉var。
雙定義
var和let,const的另一個區別是 - 後者僅僅可以被定義一次。而對於var的話,如果被同時定義多次,程式也依然會很好的執行。
var doubledVariable = 5; var doubledVariable = 6; console.log(doubledVariable); // 6
但是現在,當你用let和const來做同樣的事情,就會得到一個語法錯誤:
let doubledVariable = 5; let doubledVariable = 6;// SyntaxError: Identifier 'doubledVariable' has already been declared
但是,在巢狀的塊級作用域中,使用相同名字的變數依然會很好的工作的,這個我想大家已經清楚了,就不用過多解釋了吧。
let doubledVariable = 5; if (true) { let doubledVariable = 6; console.log(doubledVariable); // 6 } console.log(doubledVariable); // 5
不能重複定義這個功能實際上是很有用的,可以組織很多bug的發生。比如說你曾經在一個函式內,在不同地方用var定義了多個相同名稱的變數,此時之前定義的變數可能會被覆蓋,這樣對於程式碼來說無疑是一個隱患,也就是因為這樣,這個特性實際上是一個簡單的,開箱即用的解決方案。
總結
總結一下,在ES6中有兩種新方法來宣告變數:通過let和const關鍵字,除此之外,兩者都是塊級作用域,並且在宣告之前不能訪問該變數。與之前的var相比是一個主要的升級。並且會消除你很多的困擾。我提出了幾個例子,可能會幫助你節省了不少除錯的時間,但是還有更多。如果你感興趣的話,可以在網上簡單的搜尋一下。很久之前,我個人曾建議停止使用var關鍵字,所以現在我的程式碼裡充滿了let和const。我建議你也是這樣,在以後當你想改變變數的值,就使用let和const。不要再使用var了。
本文翻譯自: