給急躁的程式設計師的 JavaScript(ES2022 版本)
請支持這本書:購買捐款
(廣告,請不要封鎖。)

12 值



在本章中,我們將探討 JavaScript 具有哪些類型的值。

  支援工具:===

在本章中,我們偶爾會使用嚴格相等運算子。如果 ab 相等,則 a === b 會評估為 true。確切的含義在 §13.4.2「嚴格相等(===!==)」 中說明。

12.1 什麼是類型?

對於本章,我認為類型是值的集合——例如,類型 boolean 是集合 { false, true }。

12.2 JavaScript 的類型層級

Figure 6: A partial hierarchy of JavaScript’s types. Missing are the classes for errors, the classes associated with primitive types, and more. The diagram hints at the fact that not all objects are instances of Object.

6 顯示 JavaScript 的類型層級。我們從該圖中學到了什麼?

12.3 語言規格的類型

ECMAScript 規格僅認識總共八種類型。這些類型的名稱為(我使用 TypeScript 的名稱,而非規格的名稱)

12.4 原始值與物件

規格在值之間做出重要的區分

與 Java(在此處啟發 JavaScript)相反,原始值並非二等公民。它們與物件之間的差異較為細微。簡而言之

除此之外,原始值和物件相當類似:它們都具有屬性(鍵值條目),且可以在相同的位置使用。

接下來,我們將更深入探討原始值和物件。

12.4.1 原始值(簡稱:原語)

12.4.1.1 原語是不可變的

您無法變更、新增或移除原語的屬性

const str = 'abc';
assert.equal(str.length, 3);
assert.throws(
  () => { str.length = 1 },
  /^TypeError: Cannot assign to read only property 'length'/
);
12.4.1.2 原語是傳遞值

基本型態是傳值:變數(包含參數)儲存基本型態的內容。當將基本型態值指定給變數或作為引數傳遞給函式時,其內容會被複製。

const x = 123;
const y = x;
// `y` is the same as any other number 123
assert.equal(y, 123);

  觀察傳值和傳參考的差異

由於基本型態值是不可變的,且會依據值進行比較(請參閱下一個小節),因此無法觀察到傳值和傳遞身分識別(如用於 JavaScript 中的物件)之間的差異。

12.4.1.3 基本型態是依據值進行比較

基本型態是依據值進行比較:當比較兩個基本型態值時,我們會比較它們的內容。

assert.equal(123 === 123, true);
assert.equal('abc' === 'abc', true);

若要了解這種比較方式的特殊之處,請繼續閱讀,並找出物件是如何進行比較的。

12.4.2 物件

物件在 §28「物件」 和後續章節中有詳細說明。在此,我們主要著重於它們與基本型態值的差異。

讓我們先探討兩種建立物件的常見方式

12.4.2.1 物件預設為可變

預設情況下,您可以自由變更、新增和移除物件的屬性

const obj = {};

obj.count = 2; // add a property
assert.equal(obj.count, 2);

obj.count = 3; // change a property
assert.equal(obj.count, 3);
12.4.2.2 物件是傳遞身分識別

物件是傳遞身分識別(我的術語):變數(包含參數)儲存物件的身分識別

物件的身分識別就像一個指標(或透明的參考),指向堆疊上物件的實際資料(想像成 JavaScript 引擎的共用主記憶體)。

當將物件指定給變數或作為引數傳遞給函式時,其身分識別會被複製。每個物件文字都會在堆疊上建立一個新的物件,並傳回其身分識別。

const a = {}; // fresh empty object
// Pass the identity in `a` to `b`:
const b = a;

// Now `a` and `b` point to the same object
// (they “share” that object):
assert.equal(a === b, true);

// Changing `a` also changes `b`:
a.name = 'Tessa';
assert.equal(b.name, 'Tessa');

JavaScript 使用垃圾回收自動管理記憶體

let obj = { prop: 'value' };
obj = {};

現在 obj 的舊值 { prop: 'value' }垃圾(不再使用)。JavaScript 會在某個時間點自動垃圾回收(從記憶體中移除),(如果可用記憶體足夠,則可能永遠不會回收)。

  詳細資料:傳遞身分識別

「傳遞身分識別」表示物件的身分識別(透明的參考)會傳值。此方法也稱為 「傳遞共用」

12.4.2.3 物件是依據身分識別進行比較

物件是依身分比較(我的術語):只有當兩個變數包含相同的物件身分時,才會相等。如果它們指向內容相同的不同物件,則不相等。

const obj = {}; // fresh empty object
assert.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same content

12.5 運算子 typeofinstanceof:值的類型是什麼?

兩個運算子 typeofinstanceof 讓您可以判斷給定值 x 的類型

if (typeof x === 'string') ···
if (x instanceof Array) ···

它們有何不同?

  經驗法則:typeof 適用於原始值;instanceof 適用於物件

12.5.1 typeof

表 2:typeof 運算子的結果。
x typeof x
未定義 'undefined'
null 'object'
布林 'boolean'
數字 'number'
大整數 'bigint'
字串 'string'
符號 'symbol'
函式 'function'
所有其他物件 'object'

表 2 列出 typeof 的所有結果。它們大致對應於語言規格中的 7 種類型。唉,有兩個差異,它們是語言怪癖

以下是一些使用 typeof 的範例

> typeof undefined
'undefined'
> typeof 123n
'bigint'
> typeof 'abc'
'string'
> typeof {}
'object'

  練習:兩個關於 typeof 的練習

12.5.2 instanceof

此運算子回答以下問題:值 x 是否由類別 C 建立?

x instanceof C

例如

> (function() {}) instanceof Function
true
> ({}) instanceof Object
true
> [] instanceof Array
true

原始值不是任何東西的執行個體

> 123 instanceof Number
false
> '' instanceof String
false
> '' instanceof Object
false

  練習:instanceof

exercises/values/instanceof_exrc.mjs

12.6 類別和建構函式

JavaScript 最初的物件工廠是建構函式:如果您透過 new 運算子呼叫它們,則會傳回它們自己的「執行個體」的普通函式。

ES6 引入了類別,它們主要是建構函式的更好語法。

在這本書中,我交替使用建構函式類別這兩個術語。

類別可以視為將規格中的單一類型object分割成子類型,提供比規格中有限的 7 種更多類型。每個類別都是由它所建立的物件類型。

12.6.1 與原始類型相關的建構函式

每個原始類型(除了規格內部用於undefinednull的類型)都有相關的建構函式(類似類別)

這些函式各自扮演多個角色,例如Number

12.6.1.1 包裝原始值

與原始類型相關的建構函式也稱為包裝類型,因為它們提供了將原始值轉換為物件的標準方法。在此過程中,原始值會「包裝」在物件中。

const prim = true;
assert.equal(typeof prim, 'boolean');
assert.equal(prim instanceof Boolean, false);

const wrapped = Object(prim);
assert.equal(typeof wrapped, 'object');
assert.equal(wrapped instanceof Boolean, true);

assert.equal(wrapped.valueOf(), prim); // unwrap

包裝在實務上很少見,但語言規格內部會使用它,以賦予原始值屬性。

12.7 在類型之間轉換

在 JavaScript 中,有兩種將值轉換為其他類型的方法

12.7.1 類型之間的明確轉換

與原始類型相關的函式會明確將值轉換為該類型

> Boolean(0)
false
> Number('123')
123
> String(123)
'123'

您也可以使用Object()將值轉換為物件

> typeof Object(123)
'object'

下表更詳細地說明此轉換如何運作

x Object(x)
未定義 {}
null {}
布林值 new Boolean(x)
數字 new Number(x)
大整數 BigInt 的實例(new 會擲回 TypeError
字串 new String(x)
符號 Symbol 的實例(new 會擲回 TypeError
物件 x

12.7.2 強制轉換(類型之間的自動轉換)

在許多運算中,如果 JavaScript 的運算元/參數類型不符,它會自動轉換。這種自動轉換稱為強制轉換

例如,乘法運算元會強制轉換其運算元為數字

> '7' * '3'
21

許多內建函式也會強制轉換。例如,Number.parseInt() 會在分析參數之前,強制轉換參數為字串。這說明了以下結果

> Number.parseInt(123.45)
123

數字 123.45 在分析之前,會轉換為字串 '123.45'。分析會在第一個非數字字元之前停止,這就是結果為 123 的原因。

  練習:將值轉換為基本型別

exercises/values/conversion_exrc.mjs

  測驗

請參閱 測驗應用程式