JS對象與原始值的轉(zhuǎn)換機(jī)制

我們都知道原始值之間是可以互相轉(zhuǎn)換的,但是如果對象轉(zhuǎn)原始值呢?

  • 所有的對象在布爾上下文(context)中均為 true 。所以對于對象,不存在 to-boolean 轉(zhuǎn)換, 只有字符串和數(shù)值轉(zhuǎn)換。
  • 數(shù)值轉(zhuǎn)換發(fā)生在對象相減或應(yīng)用數(shù)學(xué)函數(shù)時。例如, Date 對象(將在 日期和時間 一章中介 紹)可以相減, date1 - date2 的結(jié)果是兩個日期之間的差值。
  • 至于字符串轉(zhuǎn)換 —— 通常發(fā)生在我們像 alert(obj) 這樣輸出一個對象和類似的上下文中。

所以,由此可見對象轉(zhuǎn)原始值的情況下大致可以分為數(shù)值轉(zhuǎn)換和字符串轉(zhuǎn)換兩種,繼續(xù)往下

什么是hint?

7.1.1.1 OrdinaryToPrimitive ( <var>O</var>, <var>hint</var> )

The abstract operation OrdinaryToPrimitive takes arguments <var>O</var> and <var>hint</var>. It performs the following steps when called:

  1. Assert: Type(<var>O</var>) is Object.

  2. Assert: <var>hint</var> is either string or number.

  3. If <var>hint</var> is string, then

    1. Let <var>methodNames</var> be ? "toString", "valueOf" ?.
  4. Else,

    1. Let <var>methodNames</var> be ? "valueOf", "toString" ?.
  5. For each element <var>name</var> of <var>methodNames</var>, doThrow a TypeError exception.

    1. Let <var>method</var> be ? Get(<var>O</var>, <var>name</var>).
    2. If IsCallable(<var>method</var>) is true, then
      1. Let <var>result</var> be ? Call(<var>method</var>, <var>O</var>).
      2. If Type(<var>result</var>) is not Object, return <var>result</var>.

我們在ECMA規(guī)范中可以得知,hint是類型轉(zhuǎn)換的變體,不太明白?沒關(guān)系,繼續(xù)往下

當(dāng)在對象到字符串的轉(zhuǎn)換中,hint的值便是"string"

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">var obj = {name:"謝絕先生"}; var anotherObj = {[obj]:"北有極光"};</pre>

當(dāng)在對象到數(shù)字的轉(zhuǎn)換中,hint的值便是"number"

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">var user = {name:"申屠肆"};
console.log(+user);</pre>

對于不確定轉(zhuǎn)換的類型時,它將依據(jù) hint的值為"default" ,例如,二進(jìn)制加法 + 可用于字符串(連接),也可以用于數(shù)字(相加),所以字符串和數(shù)字這兩 種類型都可以

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">var sth = {name:"申屠肆"};
console.log(sth + 2);</pre>

接下來,更進(jìn)一步,為了進(jìn)行轉(zhuǎn)換,JavaScript 嘗試查找并調(diào)用三個對象方法:

  1. Symbol.toPrimitive
  2. toString
  3. valueOf

在進(jìn)行類型轉(zhuǎn)換的時候,首先會去查找個名為 Symbol.toPrimitive 的內(nèi)建 symbol,像這樣

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">obj[Symbol.toPrimitive] = function(hint) { // 返回一個原始值
// hint = "string"、"number" 和 "default" 中的一個
}</pre>

該函數(shù)接收一個參數(shù) hint,hint便是需要進(jìn)行轉(zhuǎn)換的原始值的類型;通過該函數(shù)我們可以完全掌控生成什么樣的原始值,從而達(dá)到我們想要的目的,舉個栗子:

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">var sth = {
name:"申屠肆",
money:1000,
Symbol.toPrimitive { if (hint == "string") return this.name; else
return this.money;
}
}

console.log(+sth); // 1000
console.log(${sth}); // 申屠肆</pre>

古老的 toString / valueOf

如果沒有 Symbol.toPrimitive ,那么 JavaScript 將嘗試找到它們,并且按照下面的順序進(jìn)行 嘗試: 對于 “string” hint, toString -> valueOf 。 其他情況, valueOf -> toString 。 這些方法必須返回一個原始值。如果 toString 或 valueOf 返回了一個對象,那么返回值會 被忽略(和這里沒有方法的時候相同)。 默認(rèn)情況下,普通對象具有 toString 和 valueOf 方法: toString 方法返回一個字符串 "[object Object]" 。 Symbol.toPrimitive obj[Symbol.toPrimitive] = function(hint) { // 返回一個原始值 // hint = "string"、"number" 和 "default" 中的一個 } let user = { name: "John", money: 1000, Symbol.toPrimitive { 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 toString/valueOf ● valueOf 方法返回對象自身。

解釋一下:

就是說如果hint 為 "string",在沒有 Symbol.toPrimitive 的情況下,會優(yōu)先查找 toString方法;

其他情況下("default","number"),在沒有 Symbol.toPrimitive 的情況下,valueOf的優(yōu)先級高一些;

至于為什么valueOf和toString方法返回的只能是原始值的情況下,valueOf還會返回對象本身,這是一個歷史問題;

代碼實例:

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">//Node.js環(huán)境下,根目錄下 index.js文件
let user = {
name:'Joe',
money:1000,
toString() { // "hint" 為 string
console.log('執(zhí)行執(zhí)行~',this.name); return this.name;
},
valueOf() { // hint 為 default 或者 number;
return this.money;
}
}

module.exports = user;</pre>

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">//與index.js文件同目錄下的測試文件

var assert = require('chai').assert; var user = require('./index');

describe('對象轉(zhuǎn)原始值',function() {
it('hint 為 string的情況下',function() {
assert( ${user} === user.name,'成功的觸發(fā)了toString方法');
});
it('hint 為 default的情況下',function() {
assert( user + 1 === user.money + 1,'成功的觸發(fā)了valueOf方法');
});
it('hint 為 number的情況下',function() {
assert( +user === user.money,'成功的觸發(fā)了valueOf方法');
});
})</pre>

<pre style="color: rgb(0, 0, 0); font-family: "Courier New"; font-size: 12px; margin: 5px 8px; padding: 5px;">// package.json
{ "name": "something", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "test":"mocha ./index.test.js" }, "dependencies": { "chai": "^4.3.0", "mocha": "^8.2.1" }
}</pre>

單元測試結(jié)果如下:

image

2021-02-04 23:56:54
我的博客園地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容