JS 的类型转换

5,772 阅读8分钟

image.png

深夜十一点,刷着朋友圈,突然刷到这么一张图,内心:怎么样,我 JS 厉害吧 手动滑稽。

然而,在我仔细把所有的 JS 语句都看完之后,发现自己还有几个不知原因.... emmmm,应该不只有我一个人吧!

看了下,不知道原因的大部分都是 JS 的类型转换相关的问题,所以就了解了一下 JS 类型转换相关的内容。

要想搞明白这些奇奇怪怪答案的原因,我们首先要了解编程语言的** 强/弱类型,动态/静态类型 的区别**

编程语言的强弱类型,动态类型,静态类型的区别

强弱类型

强弱类型(Strong and weak typing)表示在计算机科学以及程序设计中,经常把编程语言类型系统分为强类型(英语:strongly typed)和弱类型(英语:weakly typed (loosely typed))两种。这两个术语并没有非常明确的定义,但主要用以描述编程语言对于混入不同数据类型的值进行运算时的处理方式。强类型的语言遇到函数引数类型和实际调用类型不符合的情况经常会直接出错或者编译失败;而弱类型的语言常常会实行隐式转换,或者产生难以意料的结果。
-- wikipedia

强类型 弱类型
通俗解释 强制数据定义的语言。
如果一个变量被指定了某个数据类型,如果不经过强制类型转换,那么数据类型一直不可变。
数据类型可以被忽略的区别。和强类型相反,一个变量可以被赋予不同的数据类型。
是否安全 安全 不安全
语言 C/ C++ / C# / JAVA PHP / ASP/ JavaScript / Python
🌰 整数除法运算不可用于字符串 1 + '2' = 12
严谨性能够避免一些问题 代码简洁、不需要关注变量的类型、灵活
书写繁琐 会有一些潜在的坑

静态/动态类型

动态编程语言高级编程语言的一个类别,在计算机科学领域已被广泛应用。它是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言当前非常具有活力。众所周知的ECMAScriptJavaScript)便是一个动态语言,除此之外如PHPRubyPython等也都属于动态语言,而CC++等语言则不属于动态语言。 -- wikipedia

常见语言的类型划分

image.png

因为强类型语言一般在运行时需要运行一套类型检测系统,所以强类型系统速度一般是慢于弱类型语言。
动态类型也会比静态类型速度慢些。

所以理论上C、Java、JS、Python 运行速度应该是  C > Java > JavaScript > Python

JS 数据类型

刚刚说了 JavaScript 是一种 **弱类型 **语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据。

let a = 123
a = 'abc
a = () => {}

最新的 ECMAScript 标准定义了 8 种数据类型:

这里有几个点需要注意:

  • 根据 ECMAScript 标准,JavaScript 中只有一种数字类型:基于 IEEE 754 标准的双精度 64 位二进制格式的值(-(2 -1) 到 2 -1)。它并没有为整数给出一种特定的类型
  • 一些带符号的值:+Infinity-Infinity 和 NaN (非数值,Not a Number) 都是 Nubmer 类型 。
  • 数字类型中只有一个整数有两种表示方法: 0 可表示为 -0 和 +0("0" 是 +0 的简写)
  • 对象、数组、Data、正则  都是Object
  • typeof null === 'object   在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"
typeof {a: 1} === 'object';

typeof [1, 2, 4] === 'object';

typeof new Date() === 'object';

typeof /regex/ === 'object';

// JavaScript 诞生以来便如此
typeof null === 'object';

JS 类型转换

所以,如果我们想将值从一种类型转换为另一种类型就需要类型转换。

JS 的类型转换一共分两种:显示类型转换 和 隐式类型转换。

显示类型转换

显示类型转换比较简单,通过 JS 提供的一些函数,可以直接进行转换

  • 转化为 Number 类型:Number() / parseFloat() / parseInt()
  • 转化为 String 类型:String() / toString()
  • 转化为 Boolean 类型: Boolean()

这里有几个点需要注意:

  • Number类定义的toString()方法可以接受表示转换基数的可选参数,如果不指定此参数,转换规则将是基于十进进制。

  • js中对象到字符串的转换经过了如下步骤:

    • 如果对象具有toString()方法,则调用这个方法。如果它返回一个基本类型值,js将这个值转换为字符串,并返回这个字符串。
    • 如果对象没有toString()方法,或者这个方法返回的不是一个基本类型值,那么js将调用valueOf()方法。如果存在这个方法,则调用,如果返回值是基本类型值,转换为字符串并返回
    • 否则,js无法从toString()或valueOf()获得一个基本类型值,此时将会抛出类型错误异常
  • undefined、null、false、+0、-0、NaN、""  只有这些 toBoolean()  是 false ,其余都为 true

  • Number类定义的toString()方法可以接受表示转换基数的可选参数,如果不指定此参数,转换规则将是基于十进进制。

ToPrimitive将值转为原始值

还有一个方法要介绍一下,在下文中会用到,ToPrimitive
将接收到的参数转化为原始类型:

ToPrimitive(data, PreferredType?)

input是要转换的值, PreferredType 是可选参数,ToPrimitive  运算符把其值参数转换为非对象类型。如果对象有能力被转换为不止一种原语类型(接受参数 number string ),可以使用可选的 PreferredType 来选择类型。

image.png

在执行 ToPrimitive(data,preferredType) 时如果第二个参数为空并且 data 为 Date 的事例时,此时preferredType 会
被设置为String,其他情况下preferredType都会被设置为 Number

如果 preferredType 为 Number,ToPrimitive执行过程如下:
1. 如果data为原始值,直接返回;
2. 否则调用 data.valueOf(),如果执行结果是原始值,返回之;
3. 否则调用 data.toString(),如果执行结果是原始值,返回之;
4. 否则抛异常。

如果 preferredType 为String,将上面的第2步和第3步调换,即:
1. 如果data为原始值,直接返回;
2. 否则调用 data.toString(),如果执行结果是原始值,返回之;
3. 否则调用 data.valueOf(),如果执行结果是原始值,返回之;
4. 否则抛异常。

隐式转换

这才来到本文的重点,JS 的隐式转换。也是坑了无数前端的一个地方。

JS 的隐式转换主要涉及的是两个操作符, +  和 ==

一元 +  运算符, 一元 -  运算符

image.png

一元 + 运算符如果后面是 Number 类型的话,会将其转换为 Number 类型,其实并没有什么区别。

但是如果后面不是 Number 的话,就会将调用 ToNumber  方法。

// String
const a = '123'

typeof a
"string"
const b = +a

typeof b
"number"

// Object
const c = {}

typeof c
"object"
const d = +{}

typeof d
"number"

d
NaN

加法运算符

image.png

我们可以看到,5、6 是将左侧的表达式和右侧的表达式都执行了一遍 ToPrimitive

注意第 7 条,也就是说,如果表达式左侧或者表达式右侧是 String的话,返回 toString()  之后连接而成的字符串

`1 + '1'
"11"

这里就可以解释为什么 [] + {} = [object Object]  了,聪明的你知道答案了吗?

== 抽象等运算符

image.png

主要分为 x 和 y 类型相同和类型不同的情况,类型相同时没有类型转换,类型不同时

  • x, y 为 null、undefined 两者中一个 // 返回true
  • x、y为 Number 和 String 类型时,则转换为 Number 类型比较。
  • 有 Boolean 类型时,Boolean 转化为 Number 类型比较。
  • 一个 Object 类型,一个 String 或 Number 类型,将 Object 类型进行原始转换(ToPrimitive)后,按上面流程进行原始值比较。

到此为止,开头那张图的除了 2、3、4、5 问题的答案都可以在本文找到,相信聪明的你仔细阅读完本文之后一定掌握了 JS 类型转换相关的知识,试着把其余几个问题的原因分析一下来检验自己的掌握程度吧!