W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
當對象相加 ?obj1 + obj2
?,相減 ?obj1 - obj2
?,或者使用 ?alert(obj)
? 打印時會發(fā)生什么?
JavaScript 不允許自定義運算符對對象的處理方式。與其他一些編程語言(Ruby,C++)不同,我們無法實現(xiàn)特殊的對象處理方法來處理加法(或其他運算)。
在此類運算的情況下,對象會被自動轉(zhuǎn)換為原始值,然后對這些原始值進行運算,并得到運算結(jié)果(也是一個原始值)。
這是一個重要的限制:因為 obj1 + obj2
(或者其他數(shù)學運算)的結(jié)果不能是另一個對象!
例如,我們無法使用對象來表示向量或矩陣(或成就或其他),把它們相加并期望得到一個“總和”向量作為結(jié)果。這樣的想法是行不通的。
因此,由于我們從技術上無法實現(xiàn)此類運算,所以在實際項目中不存在對對象的數(shù)學運算。如果你發(fā)現(xiàn)有,除了極少數(shù)例外,通常是寫錯了。
本文將介紹對象是如何轉(zhuǎn)換為原始值的,以及如何對其進行自定義。
我們有兩個目的:
Date
?對象)。我們稍后會遇到它們。在 類型轉(zhuǎn)換 一章中,我們已經(jīng)看到了數(shù)字、字符串和布爾轉(zhuǎn)換的規(guī)則。但是我們沒有講對象的轉(zhuǎn)換規(guī)則。現(xiàn)在我們已經(jīng)掌握了方法(method)和 symbol 的相關知識,可以開始學習對象原始值轉(zhuǎn)換了。
true
?,就這么簡單。只有字符串和數(shù)字轉(zhuǎn)換。Date
?對象(將在 日期和時間 一章中介紹)可以相減,?date1 - date2
? 的結(jié)果是兩個日期之間的差值。alert(obj)
? 這樣輸出一個對象和類似的上下文中。我們可以使用特殊的對象方法,自己實現(xiàn)字符串和數(shù)字的轉(zhuǎn)換。
現(xiàn)在讓我們一起探究技術細節(jié),因為這是深入討論該主題的唯一方式。
JavaScript 是如何決定應用哪種轉(zhuǎn)換的?
類型轉(zhuǎn)換在各種情況下有三種變體。它們被稱為 “hint”,在 規(guī)范 所述:
?"string"
?
對象到字符串的轉(zhuǎn)換,當我們對期望一個字符串的對象執(zhí)行操作時,如 “alert”:
// 輸出
alert(obj);
// 將對象作為屬性鍵
anotherObj[obj] = 123;
?"number"
?
對象到數(shù)字的轉(zhuǎn)換,例如當我們進行數(shù)學運算時:
// 顯式轉(zhuǎn)換
let num = Number(obj);
// 數(shù)學運算(除了二元加法)
let n = +obj; // 一元加法
let delta = date1 - date2;
// 小于/大于的比較
let greater = user1 > user2;
大多數(shù)內(nèi)建的數(shù)學函數(shù)也包括這種轉(zhuǎn)換。
?"default"
?
在少數(shù)情況下發(fā)生,當運算符“不確定”期望值的類型時。
例如,二元加法 +
可用于字符串(連接),也可以用于數(shù)字(相加)。因此,當二元加法得到對象類型的參數(shù)時,它將依據(jù) "default"
hint 來對其進行轉(zhuǎn)換。
此外,如果對象被用于與字符串、數(shù)字或 symbol 進行 ==
比較,這時到底應該進行哪種轉(zhuǎn)換也不是很明確,因此使用 "default"
hint。
// 二元加法使用默認 hint
let total = obj1 + obj2;
// obj == number 使用默認 hint
if (user == 1) { ... };
像 <
和 >
這樣的小于/大于比較運算符,也可以同時用于字符串和數(shù)字。不過,它們使用 “number” hint,而不是 “default”。這是歷史原因。
上面這些規(guī)則看起來比較復雜,但在實踐中其實挺簡單的。
除了一種情況(Date
對象,我們稍后會講到)之外,所有內(nèi)建對象都以和 "number"
相同的方式實現(xiàn) "default"
轉(zhuǎn)換。我們也可以這樣做。
盡管如此,了解上述的 3 個 hint 還是很重要的,很快你就會明白為什么這樣說。
為了進行轉(zhuǎn)換,JavaScript 嘗試查找并調(diào)用三個對象方法:
obj[Symbol.toPrimitive](hint)
? —— 帶有 symbol 鍵 ?Symbol.toPrimitive
?(系統(tǒng) symbol)的方法,如果這個方法存在的話,"string"
? —— 嘗試調(diào)用 ?obj.toString()
? 或 ?obj.valueOf()
?,無論哪個存在。"number"
? 或 ?"default"
? —— 嘗試調(diào)用 ?obj.valueOf()
? 或 ?obj.toString()
?,無論哪個存在。我們從第一個方法開始。有一個名為 Symbol.toPrimitive
的內(nèi)建 symbol,它被用來給轉(zhuǎn)換方法命名,像這樣:
obj[Symbol.toPrimitive] = function(hint) {
// 這里是將此對象轉(zhuǎn)換為原始值的代碼
// 它必須返回一個原始值
// hint = "string"、"number" 或 "default" 中的一個
}
如果 Symbol.toPrimitive
方法存在,則它會被用于所有 hint,無需更多其他方法。
例如,這里 user
對象實現(xiàn)了它:
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
alert(`hint: ${hint}`);
return hint == "string" ? `{name: "${this.name}"}` : this.money;
}
};
// 轉(zhuǎn)換演示:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500
從代碼中我們可以看到,根據(jù)轉(zhuǎn)換的不同,user
變成一個自描述字符串或者一個金額。user[Symbol.toPrimitive]
方法處理了所有的轉(zhuǎn)換情況。
如果沒有 Symbol.toPrimitive
,那么 JavaScript 將嘗試尋找 toString
和 valueOf
方法:
"string"
? hint:調(diào)用 ?toString
?方法,如果它不存在,則調(diào)用 ?valueOf
?方法(因此,對于字符串轉(zhuǎn)換,優(yōu)先調(diào)用 ?toString
?)。valueOf
?方法,如果它不存在,則調(diào)用 ?toString
?方法(因此,對于數(shù)學運算,優(yōu)先調(diào)用 ?valueOf
?方法)。toString
和 valueOf
方法很早己有了。它們不是 symbol(那時候還沒有 symbol 這個概念),而是“常規(guī)的”字符串命名的方法。它們提供了一種可選的“老派”的實現(xiàn)轉(zhuǎn)換的方法。
這些方法必須返回一個原始值。如果 toString
或 valueOf
返回了一個對象,那么返回值會被忽略(和這里沒有方法的時候相同)。
默認情況下,普通對象具有 toString
和 valueOf
方法:
toString
?方法返回一個字符串 ?"[object Object]"
?。valueOf
?方法返回對象自身。下面是一個示例:
let user = {name: "John"};
alert(user); // [object Object]
alert(user.valueOf() === user); // true
所以,如果我們嘗試將一個對象當做字符串來使用,例如在 alert
中,那么在默認情況下我們會看到 [object Object]
。
這里提到的默認的 valueOf
只是為了完整起見,以避免混淆。正如你看到的,它返回對象本身,因此被忽略。別問我為什么,這是歷史原因。所以我們可以假設它根本就不存在。
讓我們實現(xiàn)一下這些方法來自定義轉(zhuǎn)換。
例如,這里的 user
執(zhí)行和前面提到的那個 user
一樣的操作,使用 toString
和 valueOf
的組合(而不是 Symbol.toPrimitive
):
let user = {
name: "John",
money: 1000,
// 對于 hint="string"
toString() {
return `{name: "${this.name}"}`;
},
// 對于 hint="number" 或 "default"
valueOf() {
return this.money;
}
};
alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
我們可以看到,執(zhí)行的動作和前面使用 Symbol.toPrimitive
的那個例子相同。
通常我們希望有一個“全能”的地方來處理所有原始轉(zhuǎn)換。在這種情況下,我們可以只實現(xiàn) toString
,就像這樣:
let user = {
name: "John",
toString() {
return this.name;
}
};
alert(user); // toString -> John
alert(user + 500); // toString -> John500
如果沒有 Symbol.toPrimitive
和 valueOf
,toString
將處理所有原始轉(zhuǎn)換。
關于所有原始轉(zhuǎn)換方法,有一個重要的點需要知道,就是它們不一定會返回 “hint” 的原始值。
沒有限制 toString()
是否返回字符串,或 Symbol.toPrimitive
方法是否為 "number"
hint 返回數(shù)字。
唯一強制性的事情是:這些方法必須返回一個原始值,而不是對象。
歷史原因
由于歷史原因,如果
toString
或valueOf
返回一個對象,則不會出現(xiàn) error,但是這種值會被忽略(就像這種方法根本不存在)。這是因為在 JavaScript 語言發(fā)展初期,沒有很好的 “error” 的概念。
相反,
Symbol.toPrimitive
更嚴格,它 必須 返回一個原始值,否則就會出現(xiàn) error。
我們已經(jīng)知道,許多運算符和函數(shù)執(zhí)行類型轉(zhuǎn)換,例如乘法 *
將操作數(shù)轉(zhuǎn)換為數(shù)字。
如果我們將對象作為參數(shù)傳遞,則會出現(xiàn)兩個運算階段:
例如:
let obj = {
// toString 在沒有其他方法的情況下處理所有轉(zhuǎn)換
toString() {
return "2";
}
};
alert(obj * 2); // 4,對象被轉(zhuǎn)換為原始值字符串 "2",之后它被乘法轉(zhuǎn)換為數(shù)字 2。
obj * 2
? 首先將對象轉(zhuǎn)換為原始值(字符串 “2”)。"2" * 2
? 變?yōu)?nbsp;?2 * 2
?(字符串被轉(zhuǎn)換為數(shù)字)。二元加法在同樣的情況下會將其連接成字符串,因為它更愿意接受字符串:
let obj = {
toString() {
return "2";
}
};
alert(obj + 2); // 22("2" + 2)被轉(zhuǎn)換為原始值字符串 => 級聯(lián)
對象到原始值的轉(zhuǎn)換,是由許多期望以原始值作為值的內(nèi)建函數(shù)和運算符自動調(diào)用的。
這里有三種類型(hint):
"string"
?(對于 ?alert
?和其他需要字符串的操作)"number"
?(對于數(shù)學運算)"default"
?(少數(shù)運算符,通常對象以和 ?"number"
? 相同的方式實現(xiàn) ?"default"
? 轉(zhuǎn)換)規(guī)范明確描述了哪個運算符使用哪個 hint。
轉(zhuǎn)換算法是:
obj[Symbol.toPrimitive](hint)
? 如果這個方法存在,"string"
?obj.toString()
? 或 ?obj.valueOf()
?,無論哪個存在。"number"
? 或者 ?"default"
?obj.valueOf()
? 或 ?obj.toString()
?,無論哪個存在。所有這些方法都必須返回一個原始值才能工作(如果已定義)。
在實際使用中,通常只實現(xiàn) obj.toString()
作為字符串轉(zhuǎn)換的“全能”方法就足夠了,該方法應該返回對象的“人類可讀”表示,用于日志記錄或調(diào)試。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: