在ES6之前,JavaScript被其他程式語言詬病沒有定義常量的能力,甚至在大多數企業的開發文件中,對於常量的定義都使用var。一般經常會使用大寫字母和下劃線組成的變數名進行規範約束。當然這種妥協的“常量”是隨時可變的。例如以下程式碼:
var MAX_COUNT=0;
MAX_COUNT=1 //WARNING複製程式碼
好在E6引入了const,讓JavaScript獲得了真正的定義常量的能力,接下來小編將和大家一起學習const,通過本篇文章,你將學到以下內容:
- const介紹
- 可變的物件變數
- 如何讓物件的屬性不可變?
- 作用域範圍
- 如何選擇var/let/const
本篇文章閱讀時間預計10分鐘
const介紹
使用const語法建立變數,一旦建立初始化,我們就不能改變他們的值,因此這就稱為常量。如果你嘗試改變一個const變數,則會丟擲異常。此外,如果你使用const只宣告變數,不進行初始化,也會丟擲異常。如以下程式碼,試圖改變一個常量,引擎就會丟擲異常:
const pi = 3.141;
pi = 4; // not possible in this universe, or in other terms,
// throws Read-only error複製程式碼
由於ES6可以為程式工程化提供記憶體安全的優勢,便是因為const定義常量的原理是阻隔變數所對應的記憶體地址被改變。
變數與記憶體之間的關係由三個部分組成:變數名、記憶體繫結和記憶體地址。如下圖所示:
ES6在對變數的引用進行讀取時,會從該變數當前所對應的記憶體地址所指向記憶體空間中讀取內容。當變數改變時,引擎會重新從記憶體分配一個新的記憶體空間以儲存新值,並將新的記憶體地址與變數進行繫結。const的原理便是在變數名與記憶體地址之間建立不可變的繫結,當嘗試重新分配新的記憶體空間時,引擎便會丟擲異常。
在某些情況,並非值不可變。以V8引擎為例,如字串、數字、布林值、undined等值型別只佔用一組記憶體空間的,這些型別的值再記憶體空間中是連續的、不可拆分的。而對於物件、陣列等稀疏的引用型別值,由於屬性值是可以變化的,所以為了最快地進行記憶體排程,當物件的屬性被改變或新增了新的屬性時,都需要重新計算記憶體地址偏移值。因此使用const定義物件時,由於所建立的記憶體只繫結一處的,所以預設情況下物件這種由若干記憶體空間片段組成的值並不會全部被鎖定,因此使用const定義物件時,物件的屬性值是可變的。
可變的物件變數
上一小節我們提及到,當我們使用const定義物件時,由於物件是引用型別值,而非物件本身,因此更改物件的屬性是可行的,重新更改整個物件變數會丟擲異常,如下段程式碼所示:
const a = {
name: "Mehul"
};
console.log(a.name);
a.name = "Mohan";
console.log(a.name);
a = {}; //throws read-only exception複製程式碼
上述程式碼輸出
Mehul
Mohan
<Error thrown>複製程式碼
在此示中,a變數是引用值型別,物件地址是不能改變的,但是這個物件本身的屬性是可以改變的。因此,當我們嘗試將頂一個物件分配給a變數時,引擎就會丟擲異常。
如何讓物件的屬性值不可變呢?
上一小節,我們瞭解了,使用const定義變數時,變數的屬性是可以更改的,如何讓其不能更改呢,其實只要配合ES5中的Object.freeze()方法,便可以獲得一個第一層屬性(首層)不可變的物件。如果第一層屬性中存在物件巢狀,巢狀物件的屬性仍然是可以改變的。如下段程式碼所示:
const ob1 = {
prop1 : 1,
prop2 : {
prop2_1 : 2
}
};
Object.freeze( ob1 );
ob1.prop1 = 4; // (frozen) ob1.prop1 is not modified
ob1.prop2.prop2_1 = 4; // (frozen) modified, because ob1.prop2.prop2_1 is nested
ob1.prop2 = 4; // (frozen) not modified, bar is a key of obj1
ob1 = {}; // (const) ob2 not redeclared (used const)複製程式碼
如何實現所有層級的屬性不可變呢?我們可以用遞迴的方式呼叫Object.freeze進行實現,如下段程式碼所示(程式碼來源MDN):
function deepFreeze(object) {
// Retrieve the property names defined on object
var propNames = Object.getOwnPropertyNames(object);
// Freeze properties before freezing self
for (let name of propNames) {
let value = object[name];
object[name] = value && typeof value === "object" ?
deepFreeze(value) : value;
}
return Object.freeze(object);
}
var obj2 = {
internal: {
a: null
}
};
deepFreeze(obj2);
obj2.internal.a = 'anotherValue'; // fails silently in non-strict mode
obj2.internal.a; // null複製程式碼
作用域範圍
關於作用域的概念,小編在這篇文章《【ES基礎】let和作用域》已經介紹過了,不清楚的可以點選連結進行檢視,const和let一樣,也是塊作用域變數,他們遵循相同的作用域規則,如下段程式碼所示:
const a = 12; // accessible globally
function myFunction() {
console.log(a);
const b = 13; // accessible throughout function
if(true) {
const c = 14; // accessible throughout the "if" statement
console.log(b);
}
console.log(c);
}
myFunction();複製程式碼
上述程式碼輸出
12
13
ReferenceError Exception複製程式碼
如何選擇var/let/const
從ES6引入let的語法,設計的初衷便是替代var。從工程化的角度來說,我們應從ES6後遵從以下三原則:
- 一般情況下,使用const在定義常量。
- 只有明確值會被改變時,我們才使用let定義變數。
- 不再使用var。
結束語
今天的內容就介紹到這裡,為了更好的使用ES6,我們應該儘快適應使用const定義常量,使用let定義變數。
更多精彩內容,請微信關注”前端達人”公眾號!