typeof
和 instanceof
:值的類型是什麼?
typeof
instanceof
在本章中,我們將探討 JavaScript 具有哪些類型的值。
支援工具:
===
在本章中,我們偶爾會使用嚴格相等運算子。如果 a
和 b
相等,則 a === b
會評估為 true
。確切的含義在 §13.4.2「嚴格相等(===
和 !==
)」 中說明。
對於本章,我認為類型是值的集合——例如,類型 boolean
是集合 { false
, true
}。
圖 6 顯示 JavaScript 的類型層級。我們從該圖中學到了什麼?
Object
的實例。Object
的每個實例也是一個物件,但反之則不然。然而,您在實務中會遇到的幾乎所有物件都是 Object
的實例,例如透過物件文字建立的物件。有關此主題的更多詳細資訊說明於 §29.7.3「並非所有物件都是 Object
的實例」。ECMAScript 規格僅認識總共八種類型。這些類型的名稱為(我使用 TypeScript 的名稱,而非規格的名稱)
undefined
僅含元素 undefined
null
僅含元素 null
boolean
含元素 false
和 true
number
所有數字的類型(例如 -123
、3.141
)bigint
所有大整數的類型(例如 -123n
)string
所有字串的類型(例如 'abc'
)symbol
所有符號的類型(例如 Symbol('My Symbol')
)object
所有物件的類型(不同於 Object
,即類別 Object
及其子類別的所有實例的類型)規格在值之間做出重要的區分
undefined
、null
、boolean
、number
、bigint
、string
、symbol
的元素。與 Java(在此處啟發 JavaScript)相反,原始值並非二等公民。它們與物件之間的差異較為細微。簡而言之
除此之外,原始值和物件相當類似:它們都具有屬性(鍵值條目),且可以在相同的位置使用。
接下來,我們將更深入探討原始值和物件。
您無法變更、新增或移除原語的屬性
const str = 'abc';
.equal(str.length, 3);
assert.throws(
assert=> { str.length = 1 },
() /^TypeError: Cannot assign to read only property 'length'/
; )
基本型態是傳值:變數(包含參數)儲存基本型態的內容。當將基本型態值指定給變數或作為引數傳遞給函式時,其內容會被複製。
const x = 123;
const y = x;
// `y` is the same as any other number 123
.equal(y, 123); assert
觀察傳值和傳參考的差異
由於基本型態值是不可變的,且會依據值進行比較(請參閱下一個小節),因此無法觀察到傳值和傳遞身分識別(如用於 JavaScript 中的物件)之間的差異。
基本型態是依據值進行比較:當比較兩個基本型態值時,我們會比較它們的內容。
.equal(123 === 123, true);
assert.equal('abc' === 'abc', true); assert
若要了解這種比較方式的特殊之處,請繼續閱讀,並找出物件是如何進行比較的。
物件在 §28「物件」 和後續章節中有詳細說明。在此,我們主要著重於它們與基本型態值的差異。
讓我們先探討兩種建立物件的常見方式
物件文字
const obj = {
first: 'Jane',
last: 'Doe',
; }
物件文字以大括號 {}
開頭和結尾。它建立一個具有兩個屬性的物件。第一個屬性的鍵是 'first'
(字串),值是 'Jane'
。第二個屬性的鍵是 'last'
,值是 'Doe'
。有關物件文字的更多資訊,請參閱 §28.3.1「物件文字:屬性」。
陣列文字
const fruits = ['strawberry', 'apple'];
陣列文字以方括號 []
開頭和結尾。它建立一個包含兩個元素的陣列:'strawberry'
和 'apple'
。有關陣列文字的更多資訊,請參閱 §31.3.1「建立、讀取、寫入陣列」。
預設情況下,您可以自由變更、新增和移除物件的屬性
const obj = {};
.count = 2; // add a property
obj.equal(obj.count, 2);
assert
.count = 3; // change a property
obj.equal(obj.count, 3); assert
物件是傳遞身分識別(我的術語):變數(包含參數)儲存物件的身分識別。
物件的身分識別就像一個指標(或透明的參考),指向堆疊上物件的實際資料(想像成 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):
.equal(a === b, true);
assert
// Changing `a` also changes `b`:
.name = 'Tessa';
a.equal(b.name, 'Tessa'); assert
JavaScript 使用垃圾回收自動管理記憶體
let obj = { prop: 'value' };
= {}; obj
現在 obj
的舊值 { prop: 'value' }
是垃圾(不再使用)。JavaScript 會在某個時間點自動垃圾回收(從記憶體中移除),(如果可用記憶體足夠,則可能永遠不會回收)。
詳細資料:傳遞身分識別
「傳遞身分識別」表示物件的身分識別(透明的參考)會傳值。此方法也稱為 「傳遞共用」。
物件是依身分比較(我的術語):只有當兩個變數包含相同的物件身分時,才會相等。如果它們指向內容相同的不同物件,則不相等。
const obj = {}; // fresh empty object
.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same content assert
typeof
和 instanceof
:值的類型是什麼?兩個運算子 typeof
和 instanceof
讓您可以判斷給定值 x
的類型
if (typeof x === 'string') ···
if (x instanceof Array) ···
它們有何不同?
typeof
區分規格中的 7 種類型(減去一個遺漏,加上一個新增)。instanceof
測試哪個類別建立了給定的值。 經驗法則:
typeof
適用於原始值;instanceof
適用於物件
typeof
x |
typeof x |
---|---|
未定義 |
'undefined' |
null |
'object' |
布林 | 'boolean' |
數字 | 'number' |
大整數 | 'bigint' |
字串 | 'string' |
符號 | 'symbol' |
函式 | 'function' |
所有其他物件 | 'object' |
表 2 列出 typeof
的所有結果。它們大致對應於語言規格中的 7 種類型。唉,有兩個差異,它們是語言怪癖
typeof null
傳回 'object'
而不是 'null'
。那是個錯誤。不幸的是,無法修復。TC39 嘗試過,但它中斷了網路上的太多程式碼。typeof
應該是 'object'
(函式是物件)。為函式引入一個單獨的類別會令人困惑。以下是一些使用 typeof
的範例
> typeof undefined'undefined'
> typeof 123n'bigint'
> typeof 'abc''string'
> typeof {}'object'
練習:兩個關於
typeof
的練習
exercises/values/typeof_exrc.mjs
exercises/values/is_object_test.mjs
instanceof
此運算子回答以下問題:值 x
是否由類別 C
建立?
instanceof C x
例如
> (function() {}) instanceof Functiontrue
> ({}) instanceof Objecttrue
> [] instanceof Arraytrue
原始值不是任何東西的執行個體
> 123 instanceof Numberfalse
> '' instanceof Stringfalse
> '' instanceof Objectfalse
練習:
instanceof
exercises/values/instanceof_exrc.mjs
JavaScript 最初的物件工廠是建構函式:如果您透過 new
運算子呼叫它們,則會傳回它們自己的「執行個體」的普通函式。
ES6 引入了類別,它們主要是建構函式的更好語法。
在這本書中,我交替使用建構函式和類別這兩個術語。
類別可以視為將規格中的單一類型object
分割成子類型,提供比規格中有限的 7 種更多類型。每個類別都是由它所建立的物件類型。
每個原始類型(除了規格內部用於undefined
和null
的類型)都有相關的建構函式(類似類別)
Boolean
與布林值相關。Number
與數字相關。String
與字串相關。Symbol
與符號相關。這些函式各自扮演多個角色,例如Number
您可以將它當成函式使用,並將值轉換為數字
.equal(Number('123'), 123); assert
Number.prototype
提供數字的屬性,例如方法.toString()
.equal((123).toString, Number.prototype.toString); assert
Number
是數字工具函式的命名空間/容器物件,例如
.equal(Number.isInteger(123), true); assert
最後,您也可以將Number
當成類別使用,並建立數字物件。這些物件與實際數字不同,應避免使用。
.notEqual(new Number(123), 123);
assert.equal(new Number(123).valueOf(), 123); assert
與原始類型相關的建構函式也稱為包裝類型,因為它們提供了將原始值轉換為物件的標準方法。在此過程中,原始值會「包裝」在物件中。
const prim = true;
.equal(typeof prim, 'boolean');
assert.equal(prim instanceof Boolean, false);
assert
const wrapped = Object(prim);
.equal(typeof wrapped, 'object');
assert.equal(wrapped instanceof Boolean, true);
assert
.equal(wrapped.valueOf(), prim); // unwrap assert
包裝在實務上很少見,但語言規格內部會使用它,以賦予原始值屬性。
在 JavaScript 中,有兩種將值轉換為其他類型的方法
與原始類型相關的函式會明確將值轉換為該類型
> 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 |
在許多運算中,如果 JavaScript 的運算元/參數類型不符,它會自動轉換。這種自動轉換稱為強制轉換。
例如,乘法運算元會強制轉換其運算元為數字
> '7' * '3'21
許多內建函式也會強制轉換。例如,Number.parseInt()
會在分析參數之前,強制轉換參數為字串。這說明了以下結果
> Number.parseInt(123.45)123
數字 123.45
在分析之前,會轉換為字串 '123.45'
。分析會在第一個非數字字元之前停止,這就是結果為 123
的原因。
練習:將值轉換為基本型別
exercises/values/conversion_exrc.mjs
測驗
請參閱 測驗應用程式。