JavaScript 类型转换

这是我参与更文挑战的第 22 天,活动详情查看:更文挑战

JavaScript 是一种动态类型语言,也称弱类型语言,指的是它的变量没有类型限制,可以赋予任何类型的值。而在操作不同类型的值的时候,就会涉及到类型转换的操作(即将一种类型的值转换成另一种类型),比如将字符串'123'转换成数字123

一般我们在明确知道自己想要转换的数据类型时,可以显式地进行类型转换操作,这种类型转换行为我们称为「强制类型转换」。

而在类型不明确时,可能会出现不同类型的值进行运算操作的情况。由于运算符对数据类型是有要求的,当类型不满足要求的时候,JavaScript 就会根据一定的规则自动地进行类型转换操作,这种行为称为「隐式类型转换」。

强制类型转换

强制转换通常使用Number()String()Boolean()三个函数,手动将各种类型的值,分别转换成数字、字符串或者布尔值。

转数字类型

Number 函数如果不传参数调用,就返回 +0,如果有传参数,就调用ToNumber(value)得到结果。这个 ToNumber 是一个底层规范实现上的方法,并没有直接暴露出来。

根据规范 9.3所述,ToNumber 的结果表如下所示:

参数类型 结果
Undefined NaN
Null +0
Boolean 如果参数是 true,返回 1。参数为 false,返回 +0
Number 返回与之相等的值
String 如果可以被解析为数值,转换为相应的数值,不行的话就返回 NaN,详见例子
Object 1. primValue = ToPrimitive(input, Number)
2. 返回ToNumber(primValue)

简单示例如下所示:

// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324
Number('-Infinity') //-Infinity
Number('00000123') // 123

// 字符串:如果不可以被解析为数值,返回 NaN
Number(' 324abc ') // NaN
// parseInt 没有那么严格,会取前缀数字
parseInt(' 324abc ') // 324
// 它们都会忽略前缀和后缀空白符
parseInt('\t\v\r324abc\n') // 324
Number('\t\v\r324\n') // 324

// 空字符串转为0
Number('') // 0

// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0

// undefined:转成 NaN
Number(undefined) // NaN

// null:转成0
Number(null) // 0
复制代码

ToPromitive 方法

从结果表中可以看到,如果是用Number转换对象类型,会先调用toPrimitive方法。该方法本质是接受一个值,然后返回一个基本类型的值。参考规范9.1规范8.12.8,语法如下所示:

ToPrimitive(input[, PreferredType])
复制代码

input如果是基本类型( Undefined、Null、Boolean、Number、String),直接返回,否则进行转换。

PreferredType选填,Number 或者 String如果input是日期类型,则默认为String,否则默认为Number

如果是 ToPrimitive(obj, Number),处理步骤如下:

  1. 如果 obj 为 基本类型,直接返回;
  2. 否则,调用 valueOf 方法,如果返回一个原始值,则将其返回;
  3. 否则,调用 toString 方法,如果返回一个原始值,则将其返回;
  4. 否则,抛出一个类型错误异常。

如果是 ToPrimitive(obj, String),处理步骤如下:

  1. 如果 obj为 基本类型,直接返回;
  2. 否则,调用 toString 方法,如果返回一个原始值,则将其返回;
  3. 否则,调用 valueOf 方法,如果返回一个原始值,则将其返回;
  4. 否则,抛出一个类型错误异常。

默认情况下,对象的valueOf方法返回对象本身,所以一般总是会调用toString方法,而toString方法返回对象的类型字符串(比如[object Object]),数组的 toString 方法返回数组项拼接值,日期类型返回相应的日期字符串。

Number({}) // NaN
Number([5]) // 5
复制代码

可以改写对象的valueOftoString,从而控制该对象经过类型转换得到的值,如下所示:

let obj = {
  valueOf: function () {
    return 1;
  },
  toString: function () {
    return 2;
  }
};

Number(obj) // 1
复制代码

转字符串类型

使用String方法将类型转换成字符串类型,参考规范 9.8,如果 String 函数不传参数,返回空字符串,如果有参数,调用 ToString(value)ToString 的结果表如下所示:

参数类型 结果
Undefined “undefined”
Null “null”
Boolean 如果参数是 true,返回 “true”。参数为 false,返回 “false”
Number 转化为相应的字符串
String 返回与之相等的值
Object 1. primValue = ToPrimitive(input, String)
2. 返回ToString(primValue)

简单示例如下所示:

String() // 空字符串

String(undefined) // undefined
String(null) // null

String(false) // false
String(true) // true

String(0) // 0
String(-0) // 0
String(NaN) // NaN
String(Infinity) // Infinity
String(-Infinity) // -Infinity
复制代码

对象类型转字符串类型,也是通过 ToPrimitive函数进行了一层转化,具体转化规则见上文,优先调用对象的toString,其次使用对象的valueOf

同样,可以通过改写对象的valueOftoString,控制该对象经过类型转换得到的值,如下所示:

let obj = {
  valueOf: function () {
    return 1;
  },
  toString: function () {
    return 2;
  }
};

String(obj) // "2"
复制代码

转布尔类型

使用Boolean函数将类型转换成布尔类型,参考规范9.2,只有 6 种值可以被转换成 false,其他都会被转换成 true。

Boolean() // false
Boolean(false) // false
Boolean([])

// 只有以下6种值可以被转换为 false
Boolean(undefined) // false
Boolean(null) // false
Boolean(+0) // false
Boolean(-0) // false
Boolean(NaN) // false
Boolean("") // false
复制代码

所有对象(包括数组和函数)都转换为 true。对于包装对象也是如此。

console.log(Boolean(new Boolean(false))) // true
复制代码

隐式类型转换

当遇到以下三种情况时,JavaScript 会进行隐式类型转换。

  • 不同类型的数据进行相互运算,如1 + '1'
  • 非布尔类型的数据求布尔值,如if('123'){...}
  • 非数字类型的值使用一元操作符(+-

一般来说,操作预期的值是什么类型,就自动调用相应的类型转换函数进行类型转换。

'a' ? true : false;   // 使用 Boolean() 对 'a' 进行转换
'5' - '2'; // 使用 Number() 进行转换
+'123';  // 使用 Number() 进行转换

复制代码

另外有两个特殊的操作要进行单独分析,分别是二元运算符+ 和 非严格相等==,它们的隐式转换规则比较复杂。

二元运算符+

参考规范 11.6.1这篇文章,二元运算符+的隐式转换规则如下:

当计算 value1 + value2 时:

  1. lprim = ToPrimitive(value1)
  2. rprim = ToPrimitive(value2)
  3. 如果 lprim是字符串或者 rprim 是字符串,那么返回 ToString(lprim) ToString(rprim)的拼接结果
  4. 返回 ToNumber(lprim)ToNumber(rprim)的运算结果

几个例子如下所示:

let obj1 = {
    valueOf() {
        return 1;
    },
    toString() {
        return '2';
    }
}
let obj2 = {
    valueOf() {
        return 1;
    },
    toString() {
        return '2';
    }
}
obj1 + obj2 === 2 // true

null + 1 === 1 // true
[] + [] === "" // true
console.log({} + []); // "[object Object]"
console.log({} + {}); // "[object Object][object Object]"
复制代码

== 非严格相等

参考规范 11.9.3,非严格相等的比较规则如下表所示:

被比较值 B
Undefined Null Number String Boolean Object
被比较值 A Undefined true true false false false false
Null true true false false false false
Number false false A === B A === ToNumber(B) A=== ToNumber(B) A== ToPrimitive(B)
String false false ToNumber(A) === B A === B ToNumber(A) === ToNumber(B) A== ToPrimitive(B)
Boolean false false ToNumber(A) === B ToNumber(A) === ToNumber(B) A === B ToNumber(A) == ToPrimitive(B)
Object false false ToPrimitive(A) == B ToPrimitive(A) == B ToPrimitive(A) == ToNumber(B)

A === B

几个关键的点需要记忆:

  • UndefinedNull 与自身或对方进行==运算为true,其余为false

  • NaN 与任何数据(包括 NaN)进行==运算都为false;

  • Boolean类型与其它类型进行==运算时,隐式转换成Number进行比较;

  • StringNumberBoolean进行==运算,转换成Number进行比较;

  • 对象与其它类型比较的时候都进行 ToPrimitive 转换后进行比较。

false == undefined // false
false == [] // true
[] == ![] // true

// 以下都为 true
false == "0"
[] == 0
"" == [null]
0 == "\n"
[] == 0
+0 == -0
0 == !!null; 
0 == !!undefined;

// 字符串相关比较,都是对象的时候比较引用值
const string1 = "hello";
const string2 = String("hello");
const string3 = new String("hello");
const string4 = new String("hello");

console.log(string1 == string2); // true
console.log(string1 == string3); // true
console.log(string2 == string3); // true
console.log(string3 == string4); // false

// 由于布尔值会被转换成数字进行比较
// 所以不建议这样写 if 条件
if (a == true) {}
// 建议
if (a) {}
// 或
if (!!a) {}
复制代码

由于隐式转换是自动进行的,不容易被察觉,容易出现异常,所以建议在明确类型的使用场景下,显式地进行类型转换。

顺带提一下严格相等===Object.is不同点,

  • +0 === -0 // trueObject.is(+0, -0) // false
  • NaN === NaN // falseObject.is(NaN, NaN) // true

相关资料

数据类型的转换

JavaScript 深入之头疼的类型转换

JavaScript 中的相等性判断

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享