strat
javascript 的类型转换一直是个大坑,但其实它也减少了代码量。
ToPrimitive
Symbol.toPrimitive 是一个内置的 Symbol 值,它作为对象的函数值属性存在,当一个对象转换为原始值时,会调用此函数。
该函数被调用时,会被传递一个字符串参数 hint
,表示要转换到的原始值的预期类型。 hint
参数的取值是 "number"
、"string"
和 "default"
中的任意一个。
// 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果
let obj1 = {};
console.log(+obj1); // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"
// 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果
let obj2 = {
[Symbol.toPrimitive](hint) {
if (hint == "number") {
return 10;
}
if (hint == "string") {
return "hello";
}
return true;
}
};
console.log(+obj2); // 10 -- hint 参数值是 "number"
console.log(`${obj2}`); // "hello" -- hint 参数值是 "string"
console.log(obj2 + ""); // "true" -- hint 参数值是 "default"
从上面可以看出,toPrimitive 转换过程依靠 hint 参数:
number
: valueOf() → toString() → TypeErrorstring
: toString() → valueOf() → TypeErrordefault
: 同 number
valueOf
对象 | 返回值 |
---|---|
Array | 返回数组对象本身。 |
Boolean | 布尔值。 |
Date | 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。 |
Function | 函数本身。 |
Number | 数字值。 |
Object | 对象本身。这是默认情况。 |
String | 字符串值。 |
Symbol | Symbol本身 |
Math 和 Error 对象没有 valueOf 方法。 |
toString
对象 | 返回值 |
---|---|
Array | [1, 2, 3] => "1,2,3" |
Boolean | false => "false" |
Date | 返回表示 UTC 的字符串。 |
Function | 返回表示当前函数源代码的字符串。 |
Number | 返回表示当前数值的字符串。 |
Object | "[object Object]" |
String | 字符串本身。 |
Symbol | "Symbol()" |
注意:[null].toString()
以及[undefined].toString()
均返回空字符串""
。
ToBoolean
ES5 规范 9.2中列举了布尔强制类型转换 (ToBoolean) 会出现假值 (false) 的仅有以下几个,其余都为真值 (true):
- undefined
- null
- false
- +0、-0、NaN
- ''(空字符串)
/*
以下 a、b、c 存储的是指向对象的指针,并非假值
*/
let a = new Number(0);
let b = new Boolean(false);
let c = new String('');
Boolean(a) // true
Boolean(b) // true
Boolean(c) // true
Boolean(0) // false
Boolean(false) // false
Boolean('') // false
ToNumber
对象 | 返回值 |
---|---|
Undefined | NaN |
Null | 0 |
Boolean | true => 1, false => 0 |
Number | 返回自身 |
String | 不能解析为 StringNumericLiteral 的,均返回 NaN |
Object | ToPrimitive(input argument, hint Number) |
注: StringNumericLiteral
强制类型转换符
加号 (+)
+
作为一元运算符,单独使用,会强制将右侧操作数类型转为 number,即对右侧操作数使用 ToNumber()。
+1 // 1
+'1.2' // 1.2
+[] // 0
+[1, 2, 3] // NaN
+{} // NaN
叹号 (!)
!
会强制将右侧操作数类型转为 boolean,并反转真、假值,即对右侧操作数使用 !ToBoolean()。
!true // false
!0 // true
![] // false
!'' // true
!undefined // true
!null // true
!!true // true
!!undefined // false
!!null // false
四则运算符
加法运算遵循以下规则:
运算的其中一方为字符串,就会把另一方转换为字符串。
1 + '1' // '11' 42 + '' // '42'
如果其中一方不是字符串或数字,就会将它转换为字符串或数字。
false + true // 1 3 + [1, 2, 3] // '31,2,3' ([] + {}) // '[object Object]' /* {} + [] 的结果为 0, 是因为从左往右解析,{} 为一个代码块,+[] 被解析为将 [] 转为 number, 即 0。*/ {} + [] // 0 ({} + []) // "[object Object]"
注意:
/* 会出现以下情况,是因为 + 'b' 解释为 ToNumber('b') */ 'a' + + 'b' // "aNaN"
对于加法运算以外的运算来说,双方会被转为数字进行运算。
1 * '2' // 2
[] * {} // NaN
1 * [1, 2, 3] // NaN
let obj = {
valueOf: () => {
return 1
}
}
obj * 2 // 2
== and ===
对于==
(相对等于)、===
(绝对等于),绝大部分的书籍和博客都解释为前者仅检查值是否相等,后者检查值和类型是否相等,其实这样是不对的,正确的解释应该是:前者允许在比较的时候进行强制类型转换,后者不允许。
ES5 规范 11.9.3 定义了相对等于的行为,涵盖了所有的类型,具体可分为以下几种情况:
双方类型相同
类型 结果 Undefined true Null true Number 1. 如果其中一方为NaN,返回false。2. 如果 x 与 y 的值相同,则返回true,否则false。3.如果其中一方为+0或-0且另一方为+0或-0,返回true。 String 双方为完全相同的字符序列,返回true。否则返回 false。 Boolean 双方为true或false,返回true,否则返回false。 Object 双方引用同一个对象,返回 true。否则,返回false NaN == NaN // false -0 == +0 // true
null 与 undefined
null == undefined // true
字符串与数字
会将字符串转为数字进行比较,即
ToNumber(字符串) == 数字
。10 == '10' // true 10 == 'a' // false /* 十六进制 '0xa' => 十进制 10 */ 10 == '0Xa' // true
布尔类型与其他类型
会将布尔类型转为数字,再与其他类型进行比较,即
ToNumber(布尔类型) == 其他类型
0 == false // true '1' == true // true null == false // false undefined == false // false
对象类型与非对象类型
会将对象类型转为原始类型,再进行比较,即
ToPrimitive(对象类型) == 非对象类型
[1] == 1 // true [1, 2] == 1 // false /* b.toString() 返回 '111' */ let a = '111'; let b = Object(a); a == b // true /* null 与 undefined 不能被封装为 object, 即 Object(null) 的返回结果与 Object() 的一样 */ let c = null; let d = Object(c); c == d // false let e = undefined; let f = Object(e); e == f // false
难以理解的情况
[] == ![]
[] == ![] // true /* 第一步: !的优先级比 == 高,所以 ![] 解析为 !Boolean([]),结果为 true. 现在: [] == true 第二布: 布尔类型与其他类型进行比较,解析为 ToNumber(true), 结果为 0. 现在: [] == 0 第三步: 对象类型与非对象类型进行比较,解析为 ToPrimitive([], 'number'),结果为 0. 现在: 0 == 0 // true */
[null] == ''
[null] == '' // true [undefined] == '' // true /* [null].toString() 以及 [undefined].toString() 均返回空字符串 '' 因为 null 与 undefined 均没有 toString 和 valueOf 方法。 */
0 == '\n'
0 == '\n' // true 0 == '\t\r\n' // true /* 上述语句被解析为 ToNumber('\n'), 返回结果为 0. */
具体解释:'\n\t\r' == 0 is true?
备注
理解了类型转换,你会发现并非一定要抛弃==
去使用===
。