es6有許多特別棒的特性,你可能對該語言的總體很是熟悉,可是你知道它在內部是如何工做的嗎?當咱們知道它的內部原理之後,咱們使用起來也會更加的安心一些。這裏咱們想逐步的引導你,讓你對其有一個更深刻,更淺顯的認識。讓咱們就先從es6中的變量開始講起吧。javascript
let和constjava
在es6中新引入了兩種方式來申明變量,咱們仍然可使用廣爲傳誦的var變量(然而你不該該繼續使用它了,繼續閱讀來了解其中緣由),可是如今咱們有了兩種更牛的工具去使用:let和const。es6
let數組
let和var很是的類似,在使用方面,你可使用徹底相同的方式來聲明變量,例如:閉包
let myNewVariable = 2; var myOldVariable = 3; console.log(myNewVariable); // 2 console.log(myOldVariable); // 3
可是實際上,他們之間有幾處明顯的不一樣。他們不只僅是關鍵字變了,並且實際上它還讓會簡化咱們的一些工做,防止一些奇怪的bug,其中這些不一樣點是:函數
let是塊狀做用域(我將會在文章後面着重講一下做用域相關的東西),而var是函數做用域。工具
let不能在定義以前訪問該變量(var是能夠的,它確實是js世界中許多bug和困擾的源頭)。this
let不能被從新定義。es5
在咱們講解這些不一樣點以前,首先咱們看一個更酷的變量:const翻譯
const
const和let很是像(跟var相比來講,他們之間有許多相同點),可是他們有一個主要的不一樣點:let能夠從新賦值,可是const不能。所以const定義的變量只能有一個值,而且這個值在聲明的時候就會被賦值。所以咱們來看下下面的例子。
const myConstVariable = 2; let myLetVariable = 3; console.log(myConstVariable); // 2 myLetVariable = 4; // ok myConstVariable = 5; //wrong - TypeError thrown
可是const是徹底不可變的嗎?
有一個常見的問題:雖然變量不能被從新賦值,可是也不能讓他們真正的變爲不可變的狀態。若是const變量有一個數組或者對象做爲其值的話,你可能會像下面的代碼同樣改變它的值。
const myConstObject = {mutableProperty: 2}; // myConstObject = {}; - TypeError myConstObject.mutableProperty = 3; //ok console.log(myConstObject.mutableProperty); // 3 const myConstArray = [1]; // myConstArray = []; - TypeError myConstArray.push(2) //ok console.log(myConstArray); // [1, 2]
固然你不能用原始數據類型的值,好比string,number,boolean等,由於他們本質上是不可變的。
真正的不可變
若是你想讓咱們的變量真正的不可變的話,可使用Object.freeze(), 它可讓你的對象保持不可變。不幸的是,他僅僅是淺不可變,若是你對象裏嵌套着對象的話,它依然是可變的。
const myConstNestedObject = { immutableObject: { mutableProperty: 1 } }; Object.freeze(myConstNestedObject); myConstNestedObject.immutableObject = 10; // won't change console.log(myConstNestedObject.immutableObject); // {mutableProperty: 1} myConstNestedObject.immutableObject.mutableProperty = 10; // ok console.log(myConstNestedObject.immutableObject.mutableProperty); // 10
變量的做用域
在介紹了一些基礎知識之後,下面咱們要進入一個更高級的話題。如今咱們要開始講解es5和es6變量中的第一個不一樣-做用域
注意:下面的例子都用的是let,它的規則在const上一樣也適用
全局變量和函數做用域變量
在js中,究竟什麼是做用域呢?本文不會給出一個關於做用域的完整解釋。簡單來講,變量的做用域決定了變量的可用位置。從不一樣的角度來看,能夠說做用域是你能夠在特定區域內使用的那些變量(或者是函數)的聲明。做用域能夠是全局的(所以在全局做用域中定義的變量能夠在你代碼中任何部分訪問)或者是局部的。
很顯然,局部做用域只能在內部訪問。在ES6之前,它僅僅容許一種方式來定義局部做用域 - function,我們來看一下下面的例子:
// global scope var globalVariable = 10; function functionWithVariable() { // local scope var localVariable = 5; console.log(globalVariable); // 10 console.log(localVariable); // 5 } functionWithVariable(); //global scope again console.log(globalVariable); // 10 console.log(localVariable); // undefined
上面的例子中,變量globalVariable是全局變量,因此它能夠在咱們代碼中的函數內或者是其餘區域內被訪問到,可是變量localVariable定義在函數內,因此它只在函數內可訪問。
所以,全部在函數內建立的內容均可以在函數內被訪問到,包括函數內部裏全部的嵌套函數(可能會被嵌套多層)。在這裏可能要感謝閉包了,可是在文章裏咱們並不打算介紹它。不過請繼續關注,由於咱們在將來的博文中,會更多的介紹它。
提高
簡單來講,提高是一種吧全部的變量和函數聲明「移動」到做用域的最前面的機制。讓咱們看一下下面的例子。
function func() { console.log(localVariable); // undefined var localVariable = 5; console.log(localVariable); // 5 } func();
它爲何依然會正常工做呢?咱們尚未定義這個變量,可是它依然經過console.log()打印出了undefined。爲何不會報出一個變量未定義的錯誤呢?讓咱們再仔細看一下。
編譯變量
Javascript解析器要遍歷這個代碼兩次。第一次被稱爲編譯狀態,這一次的話,代碼中定義的變量就會提高。在他以後,咱們的代碼就變成相似於下面的這樣子的(我已經作了一些簡化,只展現出相關的部分)。
function func() { var localVariable = undefined; console.log(localVariable); // undefined localVariable = 5; console.log(localVariable); // 5 } func();
咱們看到的結果是,咱們的變量localVariable已經被移動到func函數的做用域的最前面。嚴格來講,咱們變量的聲明已經被移動了位置,而不是聲明的相關代碼被移動了位置。咱們使用這個變量並打印出來。它是undefined是由於咱們尚未定義它的值,它默認使用的undefined。
提高的例子 - 會出什麼問題
來讓咱們看一個使人討厭的例子,咱們的做用域範圍對於咱們來講,是弊大於利的。也不是說函數做用域是很差的。而是說咱們必需要警戒一些因爲提高而引發的一些陷進。咱們來看看下面的代碼:
var callbacks = []; for (var i = 0; i < 4; i++) { callbacks.push(() => console.log(i)); } callbacks[0](); callbacks[1](); callbacks[2](); callbacks[3]();
你認爲輸出的值是多少呢?你猜多是0 1 2 3,是嗎?若是是的話,對於你來講,可能會有一些驚喜。實際上,他真實的結果是4 4 4 4。等等,它到底發生了什麼?咱們來「編譯」一下代碼,代碼如今看起來就像這樣:
var callbacks; var i; callbacks = []; for (i = 0; i < 4; i++) { callbacks.push(() => console.log(i)); } callbacks[0](); callbacks[1](); callbacks[2](); callbacks[3]();
你看出問題所在了嗎?變量i在整個做用域下都是能夠被訪問到的,它不會被從新定義。它的值只會在每次的迭代中不斷地被改變。而後呢,當咱們隨後想經過函數調用打印它的值得時候,他實際上只有一個值 - 就是在最後一次循環賦給的那個值。
咱們只能這樣了嗎?不是的
Let和Const的拯救
除了定義變量的新方式之外,還引入了一種新的做用域:塊級做用域。塊就是由花括號括起來的全部的內容。因此它能夠是if,while或者是for聲明中的花括號,也能夠是單獨的一個花括號甚至是一個函數(對,函數做用域是塊狀做用域)。let和const是塊做用域。意味着不管你在塊中不管定義了什麼變量,何時定義的,它都不會跑到塊做用域外面去。咱們來看一下下面的例子:
function func() { // function scope let localVariable = 5; var oldLocalVariable = 5; if (true) { // block scope let nestedLocalVariable = 6; var oldNestedLocalVariable = 6; console.log(nestedLocalVariable); // 6 console.log(oldNestedLocalVariable); // 6 } // those are stil valid console.log(localVariable); // 5 console.log(oldLocalVariable); // 5 // and this one as well console.log(oldNestedLocalVariable); // 6 // but this on isn't console.log(nestedLocalVariable); // ReferenceError: nestedLocalVariable is not defined
你能看出來差異嗎?你能看出來怎麼使用let來解決早些時候提出問題的嗎?咱們的for循環包含一組花括號,因此它是塊做用域。因此若是在定義循環變量的時候,使用的是let或者是const來代替var的話,代碼會轉爲下面的形式。注意:我實際上已經簡化了不少,不過我肯定你能理解個人意思。
let callbacks = []; for (; i < 4; i++) { let i = 0 //, 1, 2, 3 callbacks.push(() => console.log(i)); } callbacks[0](); callbacks[1](); callbacks[2](); callbacks[3]();
如今的每一次循環都有它本身的變量定義,因此變量不會被重寫,咱們確信這行代碼能夠完成讓他作的任何事情。
這是這一部分結束的例子,可是咱們再看一下下面的例子,我相信你明白打印出來的值的緣由,以及對應的表現是什麼。
function func() { var functionScopedVariable = 10; let blockScopedVariable = 10; console.log(functionScopedVariable); // 10 console.log(blockScopedVariable); // 10 if (true) { var functionScopedVariable = 5; let blockScopedVariable = 5; console.log(functionScopedVariable); // 5 console.log(blockScopedVariable); // 5 } console.log(functionScopedVariable); // 5 console.log(blockScopedVariable); // 10 } func();
本文翻譯自:
https://blog.pragmatists.com/let-your-javascript-variables-be-constant-1633e56a948d