亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

ES6+ const

1. 前言

上一節我們學習了使用 let 取代 var 聲明變量,但是很多情況下,我們希望我們聲明的變量不能被修改。在 ES5 中不能直接聲明一個常量,如果想聲明一個不可修改的變量需要借助 defineProperty 方法。ES6 為了彌補這方面的缺失,新增了 const 語句用于聲明一個常量。本節我們還將學習到 letconst 、 var 的區別。

2. 語句使用

2.1 基本使用

const 的使用類似于 let,不同的是 const 在聲明時必須初始化一個值,而且這個值不能被改變。

const PI = 3.1415;  // 定義一個圓周率常量 PI

上面的代碼聲明了一個常量 PI,如果聲明時沒有初始化值時,則會拋出異常。

const PI;
// Uncaught SyntaxError: Missing initializer in const declaration

const 語句聲明的是一個常量,并且這個常量是不能被更改的:

const PI = 3.1415;  // 定義一個圓周率常量 PI
PI = 12             // Uncaught TypeError: Assignment to constant variable.

這里聲明了一個圓周率常量,我們知道圓周率是固定的不會被改變的,如果對 PI 重新賦值,則會拋出不能給常量分配變量的錯誤。

但如果使用 const 聲明的變量是一個對象類型的話,我們可以改變對象里的值,這是因為 const 存的變量只是一個地址的引用,所以只要不改變引用的值,就不會報錯。如下面的例子:

const obj = {};
obj.a = 12          // 12
const arr = [];
arr.push(12);       // 12
arr = {};           // Uncaught TypeError: Assignment to constant variable.

使用 const 聲明了一個對象和一個數組,然后增加對象的屬性和增加數組的值都不會報錯,這是因為我們沒有改變 obj 和 arr 的引用。如果對 arr 進行重賦值,則會報不能給常量分配變量的錯誤。

2.2 const 和 let 共有特性

由于 constlet 都是聲明變量使用的,所以他們的使用方法基本相同,下面總結一下它們共有的內容,詳情參考上一節let 的使用:

  1. 不能進行變量提升,在聲明前不能被使用,否則會拋出異常;
  2. 存在暫時性死區,在塊中不能被重復聲明。

3. ES5 模擬實現 const

在 ES6 之前是不能定義常量的,如果想定義常量的話,需要借助 ES5 中的 defineProperty 方法,這里我們寫個示例:

function setConst(key, value, obj) {
  Object.defineProperty(window, key, {
    get: function(){
      return value;
    },
    set: function(){
      console.error('Uncaught TypeError: Assignment to constant variable');
    },
  });
}
setConst('PI', 3.1415);
console.log(PI)     // 3.1415
PI = 3;             // Uncaught TypeError: Assignment to constant variable.

上面的代碼是一個定義常量的函數,使用了 ES5 的 Object.defineProperty 方法,這個方法允許在一個對象上定義一個新的屬性,具體使用詳情可以參考 ES5 的相關文檔說明。這里我們會在 window 對象上添加屬性,也可以自己定義一個對象進行添加,可以實現局部作用域的效果。通過向 setConst 方法中傳入指定的變量和值來聲明一個常量,這樣我們就在 ES5 中實現了常量的概念。由此可見,ES6 的 const 帶來的好處。

4. 場景實例

4.1 let 及 const 常見問題

在工作中經常會遇到 var、letconst 以下幾個問題:

  • 什么是變量提升?
  • 什么是暫時性死區?
  • var、letconst 區別?

這些問題在上面的講解中都有提到過,這里我們總結一下:

4.2 什么是變量提升?

變量還沒有被聲明,但是我們卻可以使用這個未被聲明的變量,這種情況就叫做提升,并且提升的是聲明。

console.log(a); // undefined
var a = 1

這個代碼其實可以寫出下面這樣的方式:

var a;
console.log(a);  // undefined
a = 1

其實變量提升就是,把變量名統一地提升到作用域的頂部進行率先定義,這也就是變量提升。不僅變量可以被提升,函數也可以被提升,并且函數的提升要優于變量的提升,函數提升會把整個函數挪到作用域頂部。

4.3 什么是暫時性死區?

暫時性死區主要是針對 let 和 const 而言的,因為它們不存在變量提升,所以在它們聲明變量之前是不能使用的,這個時候如果使用了就會報錯,這時候就形成了暫時性的死區,也就是不能被引用。

{
  console.log(name);  // ReferenceError: name is not defined. 
  let num = 100;
}

定義前被引用則會拋出異常。

4.4 var、let 及 const 區別?

上面兩個問題解決了,再看它們的區別其實就是顯而易見的,主要從以下幾個方面來分析它們之間的區別:

  • var 聲明的變量是全局作用域下的,會污染全局變量;let、const 可以和 {} 連用,產生塊作用域不會污染全局;
  • var 存在提升,我們能在聲明之前使用。let、const 因為暫時性死區的原因,不能在聲明前使用;
  • var 在同一作用域下,可以重復聲明變量;let、const 不能重復聲明變量,否則會報錯;
  • let 和 const 的用法基本一致,但是 const 聲明的變量不能再次賦值。

4.5 實例

下面的實例主要考察作用域的問題,下面的代碼輸出的結果是什么?

for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000*i);
}
// 3
// 3
// 3

代碼分析: 這里由于 setTimeout 是異步回調函數,所以 for 循環運行完后才會執行 setTimeout 內的調用棧。使用 var 聲明的變量是全局作用域的,循環完后 i 的值是 3,所以會間隔 1s 打印一個 3。想要 i 的值不被覆蓋,這時可以借助 let 的塊級作用域的特性,來解決這個問題:

for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000 * i);
}
// 0
// 1
// 2

代碼分析: 這里循環的變量 i 是 let 聲明的,當前的 i 只在本輪循環有效,所以每一次循環的 i 其實都是一個新的變量。你可能會問,如果每一輪循環的變量 i 都是重新聲明的,那它怎么知道上一輪循環的值,從而計算出本輪循環的值?這是因為在 JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量 i 時,就在上一輪循環的基礎上進行計算。

另外,for 循環還有一個特別之處,就是設置循環變量的那部分是一個父作用域,而循環體內部是一個單獨的子作用域。這樣每次定義的 i 都是局部作用域下的變量,所以在異步之后,i 的值是不會變的,所以依次打印 0 到 3 的結果。

5. 小結

本節我們學習了使用 const 來聲明一個常量,這里需要注意以下幾點:

  1. 對于不可變的變量,盡可能地使用 const 來聲明變量,如果需要更改值的時候再用 let 聲明;
  2. letconst 都只作用于塊級作用域內;
  3. letconst 聲明的變量,都不能變量提升,都存在暫存死區;
  4. letconst 聲明的變量不允許重復聲明,無論重復用 var 或者其他聲明都不行。