类型转换相关属性方法
[@@toPrimitive]
[@@toPrimitive]是内置Symbol方法,表示对象默认的类型转换行为。
目前 JavaScript 中只有Symbol.prototype、Date.prototype定义了默认方法。
Symbol.prototype[@@toPrimitive]:返回 Symbol 对象的原始值作为数据类型返回。Date.prototype[@@toPrimitive]:可以转换一个 Date 对象到一个原始值,根据入参 hint 的不同,可以返回 string 或 number 类型。
-
- 如果
hint是"string"或"default":[@@toPrimitive]()将会调用toString。如果toString属性不存在,则调用valueOf。如果valueOf也不存在,则抛出一个TypeError。 - 如果
hint是"number":[@@toPrimitive]()会首先尝试valueOf,若失败再尝试toString。
- 如果
注意:
[@@toPrimitive]()方法不需要自己调用,当对象需要被转换为原始值时,JavaScript 会自动地调用该方法。[@@toPrimitive]()方法如果存在,则必须返回原始值,返回对象会导致TypeError。
[Symbol.toPrimitive]
Symbol.toPrimitive 是内置的 symbol 属性,对应的属性值称为[@@toPrimitive]。其指定了一种接受首选类型并返回对象原始值的表示的方法。可以通过自定义对象的[@@toPrimitive]来控制类型转换行为。
系统定义的[@@toPrimitive]优先级高于自定义的[@@toPrimitive]。
当Symbol.toPrimitive的属性值调用时,会被传递一个字符串参数 hint,表示要转换到的原始值的预期类型。hint 参数的取值是 "number"、"string" 和 "default" 中的任意一个。
对应的三个取值对应着类型转换的三种算法,下面会介绍。
valueOf
除对象类型外,其他类型valueOf()都返回各自对象的原始值。
Object:转换成对象的this值String:返回String对象的原始值Date:返回Date对象的原始值Number:返回Number对象的原始值BigInt:返回BigInt对象包装的原始值Boolean:返回Boolean对象的原始值Symbol:返回Symbol对象的原始值
toString
Object:返回一个表示该对象的字符串Array:Array.prototype.toString()实际上是在内部调用了Array.prototype.join(',')Number:返回表示该数字值的字符串Date:返回一个字符串,以本地的时区表示该Date对象String:返回该字符串的值,与String.prototype.valueOf()完全相同Function:返回一个表示该函数源码的字符串
类型转换规则
上面介绍[Symbol.toPrimitive]提到hint参数的取值为 "number"、"string" 和 "default" 中的任意一个。这三个取值也表示了JavaScript 规范定义了三种类型转换的基本算法
"string":字符串类型转换"number":数字类型转换"default":原始值转换
字符串类型转换规则
@@toPrimitive → toString() → valueOf()。
数字类型转换规则
[@@toPrimitive] → valueOf() → toString()。
原始值转换规则
[@@toPrimitive] → valueOf() → toString()。
前面介绍过Date对象,hint参数为"default"时,转换规则为:@@toPrimitive → toString() → valueOf()。
返回值处理
在所有情况下,[@@toPrimitive]() 如果存在,必须可调用并返回原始值,而如果它们不可调用或返回对象,valueOf 或 toString 将被忽略。在过程结束时,如果成功,结果保证是原始值。然后,由此产生的原始值会进一步强制类型转换,具体取决于上下文。
基础类型转换规则
转换为Object
null、undefined:抛出TypeError。Number、String、Boolean、Symbol:封装成其对应的基本类型对象。
转换为String
null=>"null"undefined=>"undefined"true/false=>"true"/"false"。number的字符串化则遵循通用规则,不过那些极小和极大的数字使用指数形式:
// 1.07 连续乘以七个 1000
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
// 七个1000一共21位数字
a.toString(); // "1.07e21"
转换为Number
true/false=>1/0undefined=>NaNnull=>0string
对字符串的处理基本遵循数字常量的相关规则 / 语法。处理失败时返回 NaN(处理数字常量失败时会产生语法错误)。不同之处对以 0 开头的十六进制数并不按十六进制处理(而是按十进制)。
转换为Boolean
JavaScript 中的值可以分为以下两类:
- 可以被强制类型转换为
false的值 - 其他(被强制类型转换为 true 的值)
1. 假值
undefinednullfalse+0-0NaN""
2. 假值对象
它们都是封装了假值的对象。
var a = new Boolean(false);
var b = new Number(0);
var c = new String("");
3. 真值
真值就是假值之外的值。
Symbol类型转换
除了显示调用String()构造函数转化为字符串外,其他类型转化都会报错,抛出TypeError。
类型转换方法
String
- 构造函数调用,调用
String() - 调用
toString()方法,该方法的结果通常与String()函数返回的结果相同,null或undefined无此方法 - 模板字符串
+:+运算符即能用于数字加法,也能用于字符串拼接。如果+的其中一个操作数是字符串,则执行字符串拼接;如果其中一个操作数是对象(包括数组),则执行 原始值转换规则****
a + "" 和 String(a)的区别:
根据 原始值转换规则 ,a + "" 会按照该顺序调用[@@toPrimitive] → valueOf() → toString()。而 String(a) 则是直接调用 ToString()。
它们最后返回的都是字符串,但如果 a 是对象而非数字结果可能会不一样!
var a = {
valueOf: function() { return 42; },
toString: function() { return 4; }
};
a + ""; // "42"
String( a ); // "4"
Number
- 构造函数调用,调用
Number() toString():收表示转换基数的可选实参,如果不指定此实参,转换规则将是基于十进制- 数字运算符(
+、-、*、/):比如a - 0会将a强制类型转换为数字 toFixed():根据小数点后的指定位数将数字转换为字符串toExponential():使用指数记数法将数字转换为指数形式的字符串,其中小数点前只有一位,小数点后的位数则由参数指定(也就是说有效数字位数比指定的位数要多一位)toPrecision():根据指定的有效数字位数将数字转换成字符串。如果有效数字的位数少于数字整数部分的位数,则转换成指数形式parseInt/parseFloat:解析一个字符串并返回指定基数的十进制整数/浮点数
Boolean
- 构造函数调用,调用
Boolean() !if (..)语句中的条件判断表达式for ( .. ; .. ; .. )语句中的条件判断表达式(第二个)while (..)和do..while(..)循环中的条件判断表达式? :中的条件判断表达式- 逻辑运算符
||(逻辑或)和&&(逻辑与)左边的操作数(作为条件判断表达式)
== 和 ===
== 允许在相等比较中进行类型转换,而 === 不允许。
类型相等
基本类型
如果两个值的类型相同,就仅比较它们是否相等。例如,42等于 42,"abc" 等于 "abc"。
有几个非常规的情况需要注意。
• NaN 不等于 NaN。
• +0 等于 -0。
引用类型
两个对象指向同一个值时即视为相等,不发生强制类型转换。
类型不相等
== 在比较两个不同类型的值时会发生隐式强制类型转换,会将其中之一或两者都转换为相同的类型后再进行比较。
=== 直接返回 false。
null和undefined:在==中null和undefined相等(它们也与其自身相等),除此之外其他值都不存在这种情况。- 字符串和数字:字符串转化为数字。
- 其他类型和布尔值:布尔值转换为数字。
var a = "42";
var b = true;
a == b; // false
- 对象和非对象:
-
- 如果非对象是字符串或数字,对象调用 原始值转换规则。
- 如果非对象是布尔值,对象调用 原始值转换规则,布尔值会转换为数字。
- 其他优先级更高的规则
var a = null;
var b = Object(a);
a == b; // false
var c = undefined;
var d = Object(c);
c == d; // false
var e = NaN;
var f = Object(e);
e == f; // false
因为没有对应的封装对象,所以 null 和 undefine 不能够被封装, Object(null) 和 Object(undefined) 均返回一个常规对象。
NaN 能够被封装为数字封装对象,但拆封之后NaN == NaN返回false,因为NaN不等于NaN。
特殊情况
// 比较特殊的隐式转换
"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true
// 非常特殊的隐式转化
[] == ![] // true
根据 ToBoolean 规则,它会进行布尔值的显式类型转换(同时反转奇偶校验位),所以 [] == ![] 变成了 [] == false;布尔值 false 会隐式转换为0,变成[] == 0;[]会隐式转换为"",变成"" == 0;最后""会隐式转换为0,等式成立。
抽象关系比较
NaN和任何数比较都为false。
- 比较双方首先调用
[@@toPrimitive](),如果结果出现非字符串,就根据 数字类型转换规则 将双方强制类型转换为数字来进行比较。
var a = [42];
var b = ["43"];
a < b; // true;
b < a; // false
- 比较双方都是字符串,则按字母顺序来进行比较:
var a = ["42"];
var b = ["043"];
a < b; // false
a 和 b 并没有被转换为数字,因为 [@@toPrimitive]() 返回的是字符串,所以这里比较的是 "42"和 "043" 两个字符串,它们分别以 "4" 和 "0" 开头。因为 "0" 在字母顺序上小于 "4",所以最后结果为 false。
同理:
var a = [4, 2];
var b = [0, 4, 3];
a < b; // false
a 转换为 "4, 2",b 转换为 "0, 4, 3",同样是按字母顺序进行比较。
再比如:
var a = { b: 42 };
var b = { b: 43 };
a < b; // false
结果还是 false,因为 a 是 "[object Object]",b 也是 "[object Object]",所以按照字母顺序 a < b 并不成立。
- 比较操作符
var a = { b: 42 };
var b = { b: 43 };
a < b; // false
a == b; // false
a > b; // false
a <= b; // true
a >= b; // true
a <= b 被处理为 b < a,然后将结果反转。因为 b < a 的结果是 false,所以 a <= b 的结果是 true。
这可能与我们设想的大相径庭,即 <= 应该是“小于或者等于”。实际上 JavaScript 中 <= 是“不大于”的意思(即 !(a > b),处理为 !(b < a))。同理 a >= b 处理为 b <= a。
比较的转换规则如下。
- 如果有操作数求值为对象,根据 数字类型转换规则 转换为原始值。
- 如果在完成对象到原始值的转换后两个操作数都是字符串, 则使用字母表顺序比较这两个字符串,其中“字母表顺序”就是组 成字符串的16位
Unicode值的数值顺序。 - 如果在完成对象到原始值的转换后至少有一个操作数不是字符串,则两个操作数都会被转换为数值并按照数值顺序来比较。
0和-0被认为相等。Infinity比它本身之外的任何数都大,-Infinity比它本身之外的任何数都小。如果有一个操作数是(或转换后是)NaN,则这些比较操作符都返回false。虽然算术操作符不允许BigInt值与常规数值混用,但比较操作符允许数值与BigInt进行比较。
参考资料
- 你不知道的 JavaScript
- JavaScript 权威指南
- MDN-强制类型转换