概述
JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值。
var a = b ? 123 : '123';
但是各种运算符对数据类型是有要求的。如果运算符发现,运算子的类型与预期不符,就会自动转换类型。
比如减法运算符预期左右两侧的运算子应该是数值,如果不是,就会自动将它们转为数值:
'5' - '3' // 2
在 JavaScript 中通常将它们统称为强制类型转换,个人则倾向于用隐式强制类型转换(implicit coercion)和显式强制类型转换(explicit coercion)来区分。
相对来说,我们能够从代码中看出哪些地方是显式强制类型转换,而隐式强制类型转换则不那么明显,通常是某些操作产生的副作用。
抽象值操作
在了解强制类型转换前,我们需要掌握字符串、数字和布尔值之间类型转换的基本规则。
ToPrimitive
抽象操作 ToPrimitive,它负责处理对象到其他类型的强制类型转换。 具体转换步骤如下:
- 首先(通过内部操作 DefaultValue)检查该值是否有
valueOf()方法。 - 如果有并且返回原始类型值,就使用该返回值进行强制类型转换。
- 如果没有则检查
toString()方法,重复操作2`。 - 如果
valueOf()和toString()均不返回基本类型值,会产生 TypeError 错误。
对象、数组、函数的valueOf()和toString()
-
valueOf:对象、数组、函数的valueOf,都是返回其本身
-
toString:
- 对象:默认返回一个表示该对象的字符串,格式为 [object ObjectName];若对象有 Symbol.toStringTag 属性,其值会作为 ObjectName
const obj = { name: 'John', age: 30 }; console.log(obj.toString()); // 输出: [object Object] const obj1 = new Image(); console.log(obj1.toString()); // 输出:[object HTMLImageElement]- 数组:将数组的每个元素转换为字符串,再用英文逗号连接起来。如果数组里有 null 或者 undefined,它们会被转换为空字符串。
const arr = [1, 2, 3]; console.log(arr.toString()); // 输出: '1,2,3' const arr1 = [1, null, 3, undefined]; console.log(arr1.toString()); // 输出: '1,,3', const arr2 = []; console.log(arr2.toString()); // 输出: ''
ToString
抽象操作 ToString,它负责处理非字符串到字符串的强制类型转换,转换规则如下:
- 原始类型值
- 数值:转为相应的字符串。
- 字符串:转换后还是原来的值。
- 布尔值:
true转为字符串"true",false转为字符串"false"。 - undefined:转为字符串
"undefined"。 - null:转为字符串
"null"。
那些极小和极大的数值转字符串,使用指数形式。
String(123) // "123"
String('abc') // "abc"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
a.toString(); // "1.07e21"
-
对象
-
先调用对象自身的
toString方法。如果返回原始类型的值,则对该值进行 ToString。 -
若没有,则将对象进行 ToPrimitive操作后,再对返回值进行 ToString。
[].toString() // "" var m = {}; m.toString() // "[object Object]" -
ToNumber
抽象操作 ToNumber,它负责处理非数值到数值的强制类型转换,转换规则如下:
- 原始类型值
- 数值:转换后还是原来的值。
- 字符串:如果可以被解析为数值,则转换为相应的数值;否则返回
NaN。空字符串返回0。 - 布尔值:
true转为1,false转为0。 - undefined:转为NaN。
- null:转为0。
Number(324) // 324
Number('324') // 324
Number('324abc') // NaN
Number('') // 0
Number(true) // 1
Number(false) // 0
Number(undefined) // NaN
Number(null) // 0
parseInt
parseInt逐个解析字符,返回字符串中的数值。
parseInt解析时若遇见非数字,则解析结束;若字符串第一个元素非数字,则解析结果返回NaN。
parseInt('123bb456') // 123
parseInt('aa123bb456') // NaN
-
对象
先将对象进行 ToPrimitive操作后,再对返回值进行 ToNumber。
ToBoolean
JavaScript 中假值布尔转换为false,非假值布尔转换为true。
假值包括:
- undefined
- null
- false
- +0、-0 和 NaN
- ""
显示强制类型转换
显示强制转换为字符串、数值、布尔值的方法分别是String() 和 Number()、 Boolean()等方法。
隐示强制类型转换
隐示强制类型转换大多发生在以下场景中:
- 不同或相同类型的数据互相运算
- 对非布尔值类型的数据求布尔值
- 对非数值类型的值使用一元运算符(即+和-)
- 宽松相等
- 抽象关系比较
数据运算
除了加法运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN
如果 + 的其中一个操作数是字符串(或者通过以上步骤可以得到字符串),则执行字符串拼接;否则执行数字加法。
如果 + 的其中一个操作数是对象(包括数组),则首先对其调用ToPrimitive 抽象操作。
'12' + 34 // '1234'
34 + '56' // '3456'
[] + 34 // '34'
{} + 34 // 34
求布尔值
- if (..) 语句中的条件判断表达式。
- for ( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)。
- while (..) 和 do..while(..) 循环中的条件判断表达式。
- ? : 中的条件判断表达式。
- 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操作数(作为条件判断表达式)。
对于 || 来说,如果条件判断结果为 true 就返回第一个操作数(a 和 c)的值,如果为 false 就返回第二个操作数(b)的值。
&& 则相反,如果条件判断结果为 true 就返回第二个操作数(b)的值,如果为 false 就返 回第一个操作数(a 和 c)的值。
一元运算符
一元运算符也会把运算子转成数值。
+'abc' // NaN
-'abc' // NaN
+true // 1
-false // 0
宽松相等
宽松相等(loose equals)== 和严格相等(strict equals)=== 都用来判断两个值是否“相等”,但是它们之间有一个很重要的区别:== 允许在相等比较中进行强制类型转换,而 === 不允许。
1.字符串和数字之间的相等比较
当比较x == y(x、y为字符串或数字)时,统一为数值,再进行比较。
- 如果 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。
- 如果 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果。
2. 其他类型和布尔类型之间的相等比较
当比较x == y(x、y中有一个为布尔值)时,将布尔值转为数值,再进行比较。
- 如果 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果;
- 如果 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果。
3. null 和 undefined 之间的相等比较
在 == 中 null 和 undefined 相等(它们也与其自身相等),除此之外其他值都不存在这种情况。
null == undefined // true
null == '123' // false
undefined == '123' // false
undefined != null // false
个人认为通过这种方式将 null 和 undefined 作为等价值来处理比较好。
4. 对象和非对象之间的相等比较
当比较x == y(x、y中有一个为对象)时,将对象进行 ToPrimitive,再进行比较。
抽象关系比较
下的的规则都针对于 a < b;a <= b 被处理为 b < a,然后将结果反转
- 比较双方首先调用 ToPrimitive,如果结果出现非字符串,就根据 ToNumber 规则将双方强 制类型转换为数字来进行比较。
var a = [43];
var b = ['42'];
a < b; // false
b < a; // true
- 如果比较双方都是字符串,则按字母顺序来进行比较。
var a = [ "42" ];
var b = [ "043" ];
a < b; // false