var,let和const深刻解析(一)

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,其中這些不一樣點是:函數

  1. let是塊狀做用域(我將會在文章後面着重講一下做用域相關的東西),而var是函數做用域。工具

  2. let不能在定義以前訪問該變量(var是能夠的,它確實是js世界中許多bug和困擾的源頭)。this

  3. 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

本文轉載自:http://www.lht.ren/article/15/

相關文章
相關標籤/搜索