JavaScript-强制类型转换

453 阅读7分钟

概述

JavaScript 是一种动态类型语言,变量没有类型限制,可以随时赋予任意值。

var a = b ? 123 : '123';

但是各种运算符对数据类型是有要求的。如果运算符发现,运算子的类型与预期不符,就会自动转换类型。

比如减法运算符预期左右两侧的运算子应该是数值,如果不是,就会自动将它们转为数值:

'5' - '3' // 2

在 JavaScript 中通常将它们统称为强制类型转换,个人则倾向于用隐式强制类型转换(implicit coercion)和显式强制类型转换(explicit coercion)来区分。

相对来说,我们能够从代码中看出哪些地方是显式强制类型转换,而隐式强制类型转换则不那么明显,通常是某些操作产生的副作用。

抽象值操作

在了解强制类型转换前,我们需要掌握字符串、数字和布尔值之间类型转换的基本规则。

ToPrimitive

抽象操作 ToPrimitive,它负责处理对象到其他类型的强制类型转换。 具体转换步骤如下:

  1. 首先(通过内部操作 DefaultValue)检查该值是否有 valueOf()方法。
  2. 如果有并且返回原始类型值,就使用该返回值进行强制类型转换。
  3. 如果没有则检查toString()方法,重复操作2`。
  4. 如果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,它负责处理非字符串到字符串的强制类型转换,转换规则如下:

  1. 原始类型值
  • 数值:转为相应的字符串。
  • 字符串:转换后还是原来的值。
  • 布尔值: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"
  1. 对象

    1. 先调用对象自身的toString方法。如果返回原始类型的值,则对该值进行 ToString。

    2. 若没有,则将对象进行 ToPrimitive操作后,再对返回值进行 ToString。

    [].toString() // ""
    var m = {};
    m.toString() // "[object Object]"
    

ToNumber

抽象操作 ToNumber,它负责处理非数值到数值的强制类型转换,转换规则如下:

  1. 原始类型值
  • 数值:转换后还是原来的值。
  • 字符串:如果可以被解析为数值,则转换为相应的数值;否则返回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
  1. 对象

    将对象进行 ToPrimitive操作后,再对返回值进行 ToNumber。

ToBoolean

JavaScript 中假值布尔转换为false,非假值布尔转换为true。 假值包括:

  • undefined
  • null
  • false
  • +0、-0 和 NaN
  • ""

显示强制类型转换

显示强制转换为字符串、数值、布尔值的方法分别是String()Number()Boolean()等方法。

隐示强制类型转换

隐示强制类型转换大多发生在以下场景中:

  1. 不同或相同类型的数据互相运算
  2. 对非布尔值类型的数据求布尔值
  3. 对非数值类型的值使用一元运算符(即+和-)
  4. 宽松相等
  5. 抽象关系比较

数据运算

除了加法运算符(+)有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值。

'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