js 类型转换知多少

258 阅读12分钟

前言

相信很多同学遇到过类型转换的相关问题,这是因为 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 类。

类型转换的方式

  1. 转换为 string 类型

    原始数据类型转换之后的值
    数字类型数字类型的字符表示
    null‘null’
    undefined‘undefined’
    布尔类型true变 'true', false变 'false'
  2. 转换为 number 类型

    原始数据类型转换之后的值
    空字符 ''或""0
    非空字符串将字符内的数据内容变为数据,如果还有其他符号中文则转为NaN
    true1
    false0
    null0
    undefinedNaN
    NaN(不用转,typeof NaN 得到"number")
  3. 转换为 boolean 类型

    数据类型转换为true的值转换为false的值
    String任何非空字符串空字符串(""或’')
    Number任何非零数字0 、-0、NaN
    Object任何对象包括{}空对象null
    Undefined不适用undefined
  4. 复杂数据类型的转化

一个复杂数据类型转为基础类型的时候会调用 toPrimitive() 方法来进行转化 toPrimitive 的本质是通过调用对象自身的 valueOf() 和 toString() 方法来进行类型转换 如果传入的是 number 类型

  1. 调用对象自身的 valueOf() 若结果为原始类型(基本数据类型)则返回结果结束 toPrimitive 操作
  2. 继续调用自身的 toString(),将返回的字符串作为结果返回
  3. 若返回的仍为复杂值,则抛出异常。

若传入的是 string 类型则先调用自身的 toString,再调用 valueOf,判断逻辑保持一致

复杂类型调用 valueOf() 返回的结果

对象返回值
Array返回数组本身
Booleanboolean
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 的方法时控制台出现了以下报错:

image.png

image.png 这是因为 null、undefined是一个特殊的基本数据类型,它不是一个对象。

在访问 null 的属性或方法时,JavaScript 引擎会自动将其转换为对应的包装对象(即 Null 、Undefined对象),然后再执行相应的操作,执行完毕后,这个包装对象立即被销毁。

但是由于 null 值并没有自己的属性和方法,所以在访问它的属性或方法时会抛出一个类型错误。

并且在 js 规范中提到, 要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,并且规定 null 和 undefined 是相等的

小结

这里再附上一张类型转换图: image.png

在运算过程中需要注意的类型转换:

  • 字符串加数字,数字会转换为字符串;
  • 数字减字符串,字符串会转换为数字,如果字符串无法转换为数字则会转换为 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)

题目解析:

  1. 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

  1. {} == true
    • boolean 转化为数字 {}==1

    • {}.valueOf()不是数字也不是字符串,再调用 toString() 得到字符串

      '[object Object]' == 1

    • 字符串转为数字 '[object Object]' ==> NAN

      NaN == 1

所以 {}== true 的结果也为 false

  1. [] == ![]

    • 因为 ! 运算符的优先级比 == 高,所以先转换右边 ![],[]为对象类型,

      任何对象类型转为 boolean 值都为 true 所以 ![] ==> false ,

      最终得到 [] == false

    • 将boolean 转为数组 [] == 0

    • [].valueOf() 既不是字符也不是字符串,调用toString(),得到空字符串

      ''== 0

    • 将空串转为数字 0 最后得到 0 == 0

所以 [] == ![] 结果为 true

  1. null == undefined

这应该是让大家最意想不到的结果了 null == undefined 的结果居然是 true

这是因为在 js 规范中提到,要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,(null 和 undefined都代表着无效的值)

并且规定 null 和 undefined 是相等的

但是 null === undefined 为 false,这个就很好理解了吧,因为它俩压根儿就不是同一种数据类型。

  1. NaN == NaN

NaN 与任何值都不相等,包括它自己。因此,NaN == NaN 的结果是 false。

例题2:a ==1 && a ==2 && a==3

这里来尝试一道面试题: 如何让 A == 1 && A == 2 && A == 3 等式成立?

思想一:利用强制转换

  1. 对 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)
}
  1. 数组转换时调用 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')
}

总结:

image.png 在JavaScript中进行类型转换是非常常见的操作。 在进行类型转换时,谨慎处理,避免出现不必要的错误。

  • 隐式类型转换时,应该尽量避免使用==运算符,而应该使用===运算符来进行严格比较。
  • 显式类型转换时,应该根据实际情况选择合适的函数来进行转换。

参考

  1. 【Js】JavaScript数据类型隐式转换
  2. JS类型转换(强制类型转换+隐式类型转换)