在 JS 中,+
符號是很常見的一種,它有如下的使用狀況算法
另外一個常見的是花括號 {}
,它有兩個用途也很常見編程
除了上面說明的常見狀況外,在標準中轉換的規則還有如下幾個,要注意它的順序:operand + operand = result
數組
所以,加號運算符只能使用於原始數據類型,那麼對於對象類型的值要如何轉換爲原始數據類型?瀏覽器
JavaScript 對象轉換到基本類型值時,會使用 ToPrimitive 算法,這是一個內部算法,是編程語言在內部執行時遵循的一套規則markdown
在 ECMAScript 6th Edition #7.1.1,有一個抽象的 ToPrimitive 運算,它會用於對象轉換爲原始數據類型,這個運算不僅會用在加號運算符也會用在關係比較或值相等比較的運算中,下面是有關 ToPrimitive 的說明語法編程語言
ToPrimitive(input, PreferredType?)
input 表明代入的值,而 PreferredType 能夠是數字(Number)或字符串(String)其中一種,這會表明"優先的"、"首選的"的要進行轉換到哪種原始類型,轉換的步驟會依這裏的值而有所不一樣函數
但若沒有提供這個值也就是預設狀況,則會設置轉換的 hint 值爲
default
,這個首選的轉換原始類型的指示(hint 值),是在做內部轉換時由 JS 視狀況自動加上的,通常狀況就是預設值oop
當對象發生到基本類型值的轉換時,會按照下面的邏輯調用對象上的方法:ui
若是存在 obj[Symbol.toPrimitive],則先調用 objSymbol.toPrimitivethis
不然按下面規則來:
PreferredType 爲數字 Number
PreferredType 爲字符串 String
PreferredType 沒提供時即 hint 爲 default 時
"number"
、"string"
和 "default"
中的任意一個let ab = {
valueOf() {
return 0;
},
toString() {
return '1';
},
[Symbol.toPrimitive]() {
return 2;
}
}
console.log(1+ab); // 3
console.log('1'+ab); // 12
複製代碼
例子
// 擁有 Symbol.toPrimitive 屬性的對象
var obj2 = {
[Symbol.toPrimitive](hint) {
if(hint == "number"){
return 10;
}
if(hint == "string"){
return "hello";
}
return true;
}
}
console.log(+obj2); //10 --hint in "number"
console.log(`${obj2}`); //hello --hint is "string"
console.log(obj2 + ""); //"true"
let obj = {
[Symbol.toPrimitive](hint) {
if(hint === 'number'){
console.log('Number場景');
return 123;
}
if(hint === 'string'){
console.log('String場景');
return 'str';
}
if(hint === 'default'){
console.log('Default 場景');
return 'default';
}
}
}
console.log(2*obj); // Number場景 246
console.log(3 + obj); // Default 場景 3default
console.log(obj + ""); // Default場景 default
console.log(String(obj)); //String場景 str
複製代碼
而在 JS 的 Object 原型的設計中,都必定會有兩個 valueOf 與 toString 方法,因此這兩個方法在全部對象裏面都會有,不過它們在轉換過程當中有可能會交換被調用的順序
對於原始類型數據,toString 及 valueOf 方法的使用
const str = "hello", n = 123, bool = true;
console.log(typeof(str.toString()) + "_" + str.toString()) // string_hello
console.log(typeof(n.toString()) + "_" + n.toString()) // string_123
console.log(typeof(bool.toString()) + "_" + bool.toString()) //string_true
console.log(typeof(str.valueOf()) + "_" + str.valueOf()) //string_hello
console.log(typeof(n.valueOf()) + "_" + n.valueOf()) //number_123
console.log(typeof(bool.valueOf()) + "_" + bool.valueOf()) //boolean_true
// console.log(str.valueOf) => ƒ valueOf() { [native code] }
console.log(str.valueOf === str) // false
// console.log(n.valueOf) => ƒ valueOf() { [native code] }
console.log(n.valueOf === n) // false
// bool.valueOf() => true
console.log(bool.valueOf() === bool) // true
複製代碼
toString 方法對於原始類型數據而言,其效果至關於類型轉換,將原類型轉爲字符串;valueOf 方法對於原始類型數據而言,其效果將至關於返回原數據
複合對象類型數據使用 toString 及 valueOf 方法
var obj = {};
console.log(obj.toString()); // [object Object] 返回對象類型
console.log(obj.valueOf()); // {} 返回對象自己
複製代碼
綜合案例
const test = {
i: 10,
toString: function() {
console.log('toString');
return this.i;
},
valueOf: function() {
console.log('valueOf');
return this.i;
}
}
alert(test); // 10 toString
alert(+test); // 10 valueOf
alert(''+test); // 10 valueOf
alert(String(test)); // 10 toString
alert(Number(test)); // 10 valueOf
alert(test == '10'); // true valueOf
alert(test === '10'); // false
複製代碼
補充 toString() 和 String() 的區別
toString() 能夠將全部的數據都轉換爲字符串,但要排除 null 和 undefined,null 和 undefined 調用 toString() 方法會報錯
var num = 123;
console.log(num.toString() + '_' + typeof(num.toString())); // 123_string
console.log(num.toString(2) + '_' + typeof(num.toString())); // 1111011_string
console.log(num.toString(8) + '_' + typeof(num.toString())); // 173_string
console.log(num.toString(16) + '_' + typeof(num.toString())); //7b_string
複製代碼
注意下面兩點: Symbol.toPrimitive 和 toString 方法的返回值必須是基本類型值
valueOf 方法除了能夠返回基本類型值,也能夠返回其餘類型值
數字實際上是預設的首選類型,即在通常狀況下加號運算中的對象要做轉型時,都是先調用 valueOf 再調用 toString
注意:Date 對象的預設首選類型是字符串 String
在 JS 中所設計的 Object 純對象類型的 valueOf 與 toString 方法,它們的返回以下:
"[object Object]"
字符串值,不一樣的內建對象的返回值是 "[object type]"
字符串
type
指的是對象自己的類型識別,例如 Math 對象是返回 "[object Math]"
字符串"object"
開頭英文是小寫,後面開頭英文是大寫)所以能夠利用 Object 中的 toString 來進行各類不一樣對象進行判斷,這在之前 JS 能用的函數庫或方法很少的年代常常看到,不過它須要配合使用函數中的 call 方法,才能輸出正確的對象類型值,例如:
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call(new Date) // "[object Date]"
複製代碼
對象的這兩個方法均可被覆蓋,可用下面的代碼來觀察這兩個方法的運行順序,下面這個都是先調用 valueOf 的狀況:
let obj = {
valueOf: function () {
console.log('valueOf');
return {}; // object
},
toString: function () {
console.log('toString');
return 'obj'; // string
}
}
console.log(1 + obj); //valueOf -> toString -> '1obj'
console.log(+obj); // // valueOf -> toString -> NaN
console.log('' + obj); // valueOf -> toString -> 'obj'
複製代碼
先調用 toString 的狀況比較少見,大概只有 Date 對象或強制要轉換爲字符串時纔會看到
let obj = {
valueOf: function () {
console.log('valueOf');
return 1; // number
},
toString: function () {
console.log('toString');
return {}; // object
}
}
alert(obj); // toString -> valueOf -> alert("1");
String(obj); // toString -> valueOf -> "1";
複製代碼
而下面這個例子會形成錯誤,由於不論順序是如何都得不到原始數據類型的值,錯誤消息是"TypeError: Cannot convert object to primitive value",從這個消息中能夠得知它這裏面會須要轉換對象到原始數據類型:
let obj = {
valueOf: function () {
console.log('valueOf');
return {}; // object
},
toString: function () {
console.log('toString');
return {}; // object
}
}
console.log(obj + obj); // valueOf -> toString -> error!
複製代碼
數組 Array 很經常使用,雖然它是個對象類型,但它與 Object 的設計不一樣,它的 toString 有覆蓋,說明一下數組的 valueOf 與 toString 的兩個方法的返回值:
Function 對象不多會用到,它的 toString 也有被覆蓋,因此並非 Object 中的那個 toString,Function 對象的 valueOf 與 toString 的兩個方法的返回值:
包裝對象是 JS 爲原始數據類型數字、字符串、布爾專門設計的對象,全部的這三種原始數據類型所使用到的屬性與方法,都是在這上面提供的
包裝對象的 valueOf 與 toString 的兩個方法在原型上有通過覆蓋,因此它們的返回值與通常的 Object 的設計不一樣:
toString 方法會比較特別,這三個包裝對象裏的 toString 的細部說明以下:
"true"
或 "false"
字符串注意,常被搞混的是直接使用 Number()、String() 與 Boolean() 三個強制轉換函數的用法,這與包裝對象的用法不一樣,包裝對象是必須使用 new 關鍵字進行對象實例化的,如 new Number(123),而 Number('123') 則是強制轉換其餘類型爲數字類型的函數
Number()、String() 與 Boolean() 三個強制轉換函數所對應的就是在 ECMAScript 標準中的
ToNumber、ToString、ToBoolean
三個內部運算轉換的對照表,而當它們要轉換對象類型前,會先用上面說的 ToPrimitive 先將對象爲原始數據類型再進行轉換到所要的類型值
字符串在加號運算中有最高的優先運算,與字符串相加一定是字符串鏈接運算(concatenation)。全部的其餘原始數據類型轉爲字符串,能夠參考 ECMAScript 標準中的 ToString 對照表,如下爲一些簡單的例子
'1' + 123 // "1123"
'1' + false // "1false"
'1' + null // "1null"
1' + undefined // "1undefined" 複製代碼
數字與其餘類型做相加時,除了字符串會優先使用字符串鏈接運算(concatenation)之外,其餘都要依照數字爲優先,因此除了字符串以外的其餘原始數據類型都要轉換爲數字來進行數學的相加運算
1 + true // true 轉爲1, false 轉爲 0 -> 2
1 + null // null 轉化爲 0 -> 1
1 + undefined // undefined 轉爲NaN -> NaN
複製代碼
當數字與字符串之外的其餘原始數據類型直接使用加號運算時,就是轉爲數字再運算,這與字符串徹底無關
true + true // 2
true + null // 1
undefined + null // NaN
複製代碼
[] + [] // ""
複製代碼
""
{} + {} // "[object Object][object Object]"
複製代碼
"[object Object]"
"[object Object]"
字符串相加,依照加法運算規則第2步驟,是字符串鏈接運算(concatenation)特別注意: {} + {} 在不一樣的瀏覽器有不一樣結果
- 有些瀏覽器如 Firefox、Edge 瀏覽器會把
{} + {}
直譯爲至關於+{}
語句,由於它們會認爲以花括號開頭({)
的是一個區塊語句的開頭而不是一個對象字面量,因此會略過第一個{}
,把整個語句認爲是個+{}
的語句- 至關於強制求出數字值的
Number({})
函數調用運算,即Number("[object Object]")
,最後得出的是NaN
若在第一個空對象加上圓括號
(())
,這樣 JS 就會認爲前面是個對象,就能夠得出一樣的結果:({}) + {} // "[object Object][object Object]" 複製代碼
或是分開來先聲明對象的變量值也能夠得出一樣的結果,像下面這樣:
let foo = {}, bar = {}; foo + bar; 複製代碼
注: 上面說的行爲與加號運算的對象字面值是否是個空對象無關,就算是裏面有值的對象,如 {a:1, b:2} 也是一樣的結果
上面所述的把 {} 看成區塊語句的狀況在此也會發生,不過此次全部的瀏覽器都會有一致結果,若 {}
在前面,而 []
在後面,則前面(左邊)那個運算元會被認爲是區塊語句而不是對象字面量
因此 {} + []
至關於 +[]
語句,即至關於強制求出數字值的 Number([])
運算,即 Number("")
運算,最後得出的是 0
數字
{} + [] // 0
{a: 1} + [1,2] // +"1,2" -> NaN
複製代碼
特別注意:若第一個是
{}
時,後面加上其餘的像數組、數字或字符串
,這時加號運算會直接變爲一元正號運算,也就是強制轉爲數字的運算,這是個陷阱要當心
[] + {} // "" + "[object Object]" -> "[object Object]"
[1, 2] + {a:1} // "1,2[object Object]"
複製代碼
Date 對象的 valueOf 與 toString 的兩個方法的返回值:
Date 對象上面有說起是首選類型爲"字符串"的一種異常的對象,這與其餘的對象的行爲不一樣(通常對象會先調用 valueOf 再調用 toString ),在進行加號運算時它會優先使用 toString 來進行轉換,最後一定是字符串鏈接運算(concatenation),如如下的結果:
1 + (new Date()) // "1Mon May 17 2021 02:06:39 GMT+0800 (中國標準時間)"
複製代碼
要得出 Date 對象中的 valueOf 返回值,須要使用一元加號 +
來強制轉換它爲數字類型,如如下的代碼:
+new Date() // 1621188445596
複製代碼
ES6 中新加入的 Symbol 數據類型,它不算是通常的值也不是對象,它並無內部自動轉型的設計,因此徹底不能直接用於加法運算,使用時會報錯