前言
相信很多同学遇到过类型转换的相关问题,这是因为 JavaScript 是一种动态类型语言,变量的类型可以在运行时进行改变。 所以当我们在编写代码时,经常需要对变量进行类型转换。 本文就来深入探讨一下 JavaScript 中的类型转换,并介绍如何正确地进行类型转换。
js 中常见的数据类型
在 js 中我们用到的数据类型分为基本数据类型及引用数据类型:
- 常见的基本数据类型有
- string、number、boolean、undefined、null、symbol
- 基本数据类型按值存储,存放在栈内存中
- 当基本类型进行拷贝时,值从一个内存空间复制到了另一个内存空间,为值的拷贝
- 引用数据类型也称为复杂数据类型:
- 包括 Object、Array
- 引用数据类型在内存中按引用存储的,引用数据类型会在栈内存中存储数据的地址,而该地址指向的对象或数组则是保存在堆内存中。
- 引用类型在进行拷贝时拷贝的是栈中存放的地址(浅拷贝)
注意: JavaScript 中的栈内存和堆内存是两个不同的概念。栈内存用于存储基本数据类型和引用类型变量名所对应的地址,而堆内存用于存储引用类型的实际内容。 关于数据拷贝相关知识点可以阅读 深拷贝 & 浅拷贝 的应用与实现 这篇文章。
什么是类型转换?
对于不同的类型之间的运算需要先对数据的类型进行转换, 在 JavaScript 有两种类型转换的方式,分别是隐式类型转换和强制类型转换(显式类型转换)。
隐式类型转换
隐式类型转换,通常发生在一些数学运算中,JavaScript 解释器会在运算之前将它们的类型自动进行转换。 当运算符两边的数据类型不一致时,JavaScript 解释器会自动将其中一个数据类型转换为另一个数据类型,以便进行计算。
举个🌰:
let x = 10;
let y = "5";
let z = x + y; // 15
当字符串和数字进行加法运算时,JavaScript 会将字符串转换为数字,再进行加法运算。 这种类型转换是自动发生的,无需手动转换因此也被称为自动转换。
除了加法运算符之外,JavaScript中还有很多其他的运算符也会进行隐式类型转换。例如:
- 算术运算符:加(+)、减(-)、乘(*)、除(/)、取模(%);
- 逻辑运算符:逻辑与(&&)、逻辑或(||)、逻辑非(!);
- 字符串运算符:+、+=。
隐式转换规则
比较运算符
在使用比较运算符(==、!=、===、!==、<、<=、>、>=)进行比较时,如果比较的两个操作数类型不同,JavaScript 会尝试将它们转换为相同的类型,然后再进行比较。具体转换规则如下:
- 如果其中一个为布尔值,则将其转换为数字( true 转换为1,false 转换为 0)。
- 如果其中一个操作数是字符串,另一个是数字,则将字符串转换为数字。
- 如果其中一个操作数是对象,另一个操作数不是对象,则将对象转换为原始值,然后再进行比较。
- 如果其中一个操作数是 null,另一个操作数是 undefined ,则它们相等。
简单数据类型的比较:
"0" == null; // false
"0" == undefined; // false
"0" == false; // true -- 噢!
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 噢!
false == ""; // true -- 噢!
false == []; // true -- 噢!
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 噢!
"" == []; // true -- 噢!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 噢!
0 == {}; // false
复杂数据类型的比较:
var a = { b: 42 };
var b = { b: 43 };
a < b; // ??
a < b; // false
a == b; // false
a > b; // false
a <= b; // true
a >= b; // true
算术运算符
当使用算数运算符(+、-、*、/、%)进行运算时,如果操作数类型不同,JavaScript会尝试将它们转换为相同的类型,然后再进行运算:
- 如果两个操作数都是字符串,则直接进行拼接。
- 如果其中一个操作数是布尔值,则将其转换为数字(true 转换为 1,false 转换为 0)。
- 如果其中一个操作数是对象,则将其转换为原始值,然后再进行运算。
console.log("3" - 2); // 1
console.log("3" + 2); // "32"
console.log(3 + "2"); // "32"
console.log("3" * "2"); // 6
console.log("10" / "2"); // 5
console.log(1 + true); // 2
console.log(1 + false); // 1
console.log(1 + undefined); // NaN
console.log(3 + null); // 3
console.log("3" + null); // "3null"
console.log(true + null); // 1
console.log(true + undefined); // NaN
逻辑运算符
当使用逻辑运算符(&&、||、!)进行运算时,JavaScript会先将操作数转换为布尔值,然后再进行运算。具体转换规则如下:
- 如果操作数是0、-0、null、false、NaN或空字符串(""),则转换为false。
- 如果操作数是对象或非空字符串,则转换为true。
- 使用 !! 操作符可以将一个数强制转换为 boolean 类。
类型转换的方式
-
转换为 string 类型
原始数据类型 转换之后的值 数字类型 数字类型的字符表示 null ‘null’ undefined ‘undefined’ 布尔类型 true变 'true', false变 'false' -
转换为 number 类型
原始数据类型 转换之后的值 空字符 ''或"" 0 非空字符串 将字符内的数据内容变为数据,如果还有其他符号中文则转为NaN true 1 false 0 null 0 undefined NaN NaN (不用转,typeof NaN 得到"number") -
转换为 boolean 类型
数据类型 转换为true的值 转换为false的值 String 任何非空字符串 空字符串(""或’') Number 任何非零数字 0 、-0、NaN Object 任何对象包括{}空对象 null Undefined 不适用 undefined -
复杂数据类型的转化
一个复杂数据类型转为基础类型的时候会调用 toPrimitive() 方法来进行转化 toPrimitive 的本质是通过调用对象自身的 valueOf() 和 toString() 方法来进行类型转换 如果传入的是 number 类型
- 调用对象自身的 valueOf() 若结果为原始类型(基本数据类型)则返回结果结束 toPrimitive 操作
- 继续调用自身的 toString(),将返回的字符串作为结果返回
- 若返回的仍为复杂值,则抛出异常。
若传入的是 string 类型则先调用自身的 toString,再调用 valueOf,判断逻辑保持一致
复杂类型调用 valueOf() 返回的结果
对象 | 返回值 |
---|---|
Array | 返回数组本身 |
Boolean | boolean |
Date | 相当于调用了getTime() 即 new Date()).valueOf() -->1626695004310 |
Function | 函数本身 |
Number | 数字值 |
Object | 对象本身 |
String | 字符串 |
注意 Undefined、Null 没有 valueOf 方法
复杂类型调用 toString() 返回的结果
对象 | 返回值 |
---|---|
Array | 逗号拼接的字符串,toString() === join() |
Boolean | 字符串形式的 true 或 false |
Date | “Mon Jul 19 2021 18:46:05 GMT+0800 (中国标准时间)” |
Function | 函数的文本定义 |
Number | 数字转成的字符串 |
Object | 将对象转换为字符串 “[object Object]” |
String | 字符串本身 |
关于包装对象
由于基本数据类型,所以它们并没有自己的属性和方法。 所以在调用它们的 toString() 方法时,JavaScript 会自动将它们转换为对应的包装对象 例如:
const num = 123;
console.log(num.toString()); // 输出:123
由于 num 是一个基本数据类型,JavaScript 引擎会自动将它转换为对应的包装对象 Number,然后再调用这个对象的 toString() 方法。
但是需要注意的是,这些包装对象并不是我们在代码中显式创建的对象,而是由 JavaScript 引擎在运行时自动创建的临时对象,它们并不会保留在内存中,执行完毕后,这个包装对象立即被销毁。
关于 null、undefined
我们在尝试调用 null 和 undefined 的方法时控制台出现了以下报错:
这是因为 null、undefined是一个特殊的基本数据类型,它不是一个对象。
在访问 null 的属性或方法时,JavaScript 引擎会自动将其转换为对应的包装对象(即 Null 、Undefined对象),然后再执行相应的操作,执行完毕后,这个包装对象立即被销毁。
但是由于 null 值并没有自己的属性和方法,所以在访问它的属性或方法时会抛出一个类型错误。
并且在 js 规范中提到, 要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,并且规定 null 和 undefined 是相等的。
小结
这里再附上一张类型转换图:
在运算过程中需要注意的类型转换:
- 字符串加数字,数字会转换为字符串;
- 数字减字符串,字符串会转换为数字,如果字符串无法转换为数字则会转换为 NaN;
- 字符串减数字,字符串会转换为数字,如果字符串无法转换为数字,则会转换为 NaN;
- 乘、除运算时,也会先将字符串转换为数字。
- 还有一个特别的值 NaN, NaN 与任何值都不相等,包括它自己。
强制类型转换
强制类型转换也称为显示类型转换,与隐式类型转换相反,强制类型转换需要手动进行,通过 JavaScript 提供的一些内置函数来进行显式类型转换,将一个数据类型转换为另一个数据类型, 例如 Number()、String()、parseInt()等。
Number()
Number()方法可以将任何数据类型转换为数字 使用方法如下
console.log(Number("10.5")); // 10.5
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(null)); // 0
- 如果参数是一个字符串,则会尝试将其解析为数字,会忽略字符串前后的空格。
- 如果字符串第一个字符为 -,那么转换结果将为负数,如果字符串第一个字符为正号(+),那么转换后将忽略正号。
- 如果参数是一个布尔值,则true会被转换为1,false会被转换为0。
- 如果参数是null,则会被转换为0。如果参数是undefined,则会被转换为NaN。
String()
String()方法可以将任何数据类型转换为字符串
String(3.14); // "3.14"
String(true); // "true"
String(null); // "null"
let u;
let str = String(u); // "undefined"
- 如果参数是一个数字,则会将其转换为字符串。
- 如果参数是一个布尔值,则true会被转换为"true",false会被转换为"false"。
- 如果参数是null,则会被转换为"null"。
- 如果参数是undefined,则会被转换为"undefined"
parseInt()
parseInt() 函数的语法格式如下:
parseInt(string, radix);
- string 为要转换的值,如果参数不是一个字符串,则会先将其转换为字符串,字符串开头的空白将会忽略;
- radix 为一个可选参数,表示字符串的进制基数,取值范围在 2 到 36 之间,默认为十进制数
console.log(parseInt("1101",2)); // 13
console.log(parseInt("a37f",16)); // 41855
console.log(parseInt("123")); // 123
console.log(parseInt(" 123")); // 123
在使用 parseInt() 函数时,有以下几点需要注意:
- 如果字符串前面为除空格、+、- 、 a~f(或 A~F)之外的非数字字符,那么字符串将不会被解析,返回结果为 NaN;
- 在字符串中包含空格、小数点(.)等特殊符号或非数字的字符时,解析将在遇到这些字符时停止,并返回已解析的结果;
- 如果字符串是空字符串,返回结果为 NaN。
例题1:写出下面几个等式的输出结果
根据上面的转换规则大家动手试一试吧:
console.log(new String('abc') == true)
console.log({} == true)
console.log([] == ![])
console.log(null == undefined)
console.log(NAN == NaN)
题目解析:
- new String('abc') == true
-
boolean 转化为数字 newString('abc')==1
-
new String('abc').valueOf()不是数字也不是字符串,再调用toString()
'abc' ==1
-
字符串转数字 'abc' 转为 NAN
NaN==1 NaN和任何类型比较都为false
-
所以 new String('abc') == true 的结果为 false
- {} == true
-
boolean 转化为数字 {}==1
-
{}.valueOf()不是数字也不是字符串,再调用 toString() 得到字符串
'[object Object]' == 1
-
字符串转为数字 '[object Object]' ==> NAN
NaN == 1
-
所以 {}== true 的结果也为 false
-
[] == ![]
-
因为 ! 运算符的优先级比 == 高,所以先转换右边 ![],[]为对象类型,
任何对象类型转为 boolean 值都为 true 所以 ![] ==> false ,
最终得到 [] == false
-
将boolean 转为数组 [] == 0
-
[].valueOf() 既不是字符也不是字符串,调用toString(),得到空字符串
''== 0
-
将空串转为数字 0 最后得到 0 == 0
-
所以 [] == ![] 结果为 true
- null == undefined
这应该是让大家最意想不到的结果了 null == undefined 的结果居然是 true
这是因为在 js 规范中提到,要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,(null 和 undefined都代表着无效的值)
并且规定 null 和 undefined 是相等的。
但是 null === undefined 为 false,这个就很好理解了吧,因为它俩压根儿就不是同一种数据类型。
- NaN == NaN
NaN 与任何值都不相等,包括它自己。因此,NaN == NaN 的结果是 false。
例题2:a ==1 && a ==2 && a==3
这里来尝试一道面试题: 如何让 A == 1 && A == 2 && A == 3 等式成立?
思想一:利用强制转换
- 对 tostring() 或 valueOf()进行重写
在类型转换时会调用 tostring() 或者 valueOf()
const a ={
value: 1
toString(){
return value++
}
}
const a ={
value: 1
valueOf(){
return value++
}
}
if (a == 1 && a == 2 && a == 3) {
console.log(a)
}
- 数组转换时调用 tostring()
而数组上的 toString 方法和 join 方法存在一定的联系具体表现为:
Array.prototype.toString() === Array.prototype.join()
所以 重写数组的 toString 方法
const a =[1,2,3]
a.toString = a.shift
if (a == 1 && a == 2 && a == 3) {
console.log(a)
}
思想二: 数据劫持 getter
在比较时 通过 get() 方法获取 a 的值
let value = 1
Object.defineProperty(this,'a',{
get(){
return value++
}
})
if (a == 1 && a == 2 && a == 3) {
console.log('ok')
}
总结:
在JavaScript中进行类型转换是非常常见的操作。
在进行类型转换时,谨慎处理,避免出现不必要的错误。
- 隐式类型转换时,应该尽量避免使用==运算符,而应该使用===运算符来进行严格比较。
- 显式类型转换时,应该根据实际情况选择合适的函数来进行转换。