【JS基础】类型转换知多少

1,814 阅读7分钟

开胃菜

先说一个题外话,我在工作中遇到一个问题,需要比较 "08:00""09:00" 的大小,最后我找到三种方法:

  1. 在两个字符串前后各拼接相同的年月日和秒,拼成完整的时间格式进行比较:
var head = "2016-01-01 "
var foot = ":00"

var time1 = head + "08:00" + foot //"2016-01-01 08:00:00"
var time2 = head + "09:00" + foot //"2016-01-01 09:00:00"

剩下的就不说了,比较两个完整的日期还是很容易的。

  1. 把两个字符串中的冒号去掉,转换成数字进行比较:
function timeToNumber(time) {
    let [head,foot] = time.split(":")
    return Number(head+foot)
}

var time1 = timeToNumber("08:00") //800
var time2 = timeToNumber("09:00") //900
  1. 直接比较

对,你没有看错,直接比较两个字符串:

"08:00" > "09:00" //false

看到这里估计有人就纳闷了,很明显第三种方法是更简洁的,但是字符串比较,好像很少见,它比较的依据是什么呢?

其实,字符串比较大小,会从左到右依次取两个字符串中的字符,两两比较他们charCodeAt()的结果,直到比较出大小就停止。比如:

var str1 = "a11"
var str2 = "a2"
// str1 和 str2 比较的时候,会先比较 str1[0] 和 str2[0],两个都是 "a",比较下一个
// str1[1] 是"1",charCodeAt()是49,str2[1] 是"2",结果是50,所以 str1[1] < str2[1],对比结束
// 最终结果 str1 < str2 

同理,在比较"08:00""09:00"的时候,先比较两个"0",发现一致之后比较"8""9",所以"08:00" < "09:00"

这里有一个问题就是,时间格式必须保持一致,位数不够的记得补"0",拿"8:00""10:00"比较会发现结果有问题,必须拿"08:00""10:00"比较才可以。

这个问题就说到这里,大家有其他的方法可以留言补充,给大家提供不同的思路。开胃菜结束,进入正题。

正题

作为一个爱(记)学(不)习(清)的好(笨)孩子,通过字符串比较这件事,我意识到还有更多的非相同类型的比较,比如字符串和数字的比较,布尔和数组的比较(我疯了么我这么用),另外还有加减乘除等其他操作符。

我觉得有必要整理一下了。

我第一反应是这张图:

真是迷人的笑容呢 :)

在比较之前,我们需要先了解下各种数据类型转化的结果有哪些。

转数字

  • 字符串:
    • 空字符串是0
    • 字符串头尾有空格会忽略
    • 空格在中间,或者字符串中含有非数字类型字符,转换结果就是NaN
  • 布尔:true -> 1, false -> 0
  • undefined字: NaN
  • null: 0
  • 数组:
    • 空数组是0
    • 如果数组中有且只有一项是数字元素,转换为数字
    • 其他情况NaN
  • 对象:
    • 如果对象有valueOf()方法,就调用该方法。如果返回基本类型值,就将这个值转化为数字
    • 如果对象没有valueOf()方法或者该方法返回的不是基本类型值,就会调用该对象的toString()方法。如果存在且返回值是基本类型值,就转化为数字
    • 否则就报错
  • 函数:NaN

转字符串

  • undefined -> "undefined"
  • null ->"null"
  • true -> "true" / false ->"false"
  • 数字:极小和极大的数字使用指数形式,一般的情况你懂得
  • 对象:
    • 如果对象有toString()方法,就调用toString()方法。如果该方法返回基本类型值,就将这个值转化为字符串
    • 如果对象没有toString()方法或者该方法返回的不是基本类型值,就会调用该对象的valueOf()方法。如果存在且返回值是基本类型值,就转化为字符串
    • 否则就报错
    • 除非自行定义,否则toString()返回内部属性[[Class]]的值,如"[object Object]"

转布尔

  • 所有的假值(undefinednull+0-0NaN"")会被转化为 false,其他都会被转为true
  • 所以,空对象、空数组都是true

转对象

  • nullundefined转对象直接抛异常
  • 基本类型通过调用String()Number()Boolean()构造函数,转换为他们各自的包装对象

使用场景

知道了各种数据类型转化的规则,那么在不同的场景中,究竟是怎么使用的呢?

== 运算符

常见的误区是:==检查值是否相等,===检查值和类型是否相等。

正确的解释是:==允许在相等比较中进行强制类型转换,而===不允许。

事实上,=====都会检查操作数的类型,区别在于类型不同时它们的处理方式不同。

  1. 如果一个值是null,另一个值是undefined,则相等
  2. 如果一个是字符串,另一个值是数字,则把字符串转换成数字,进行比较
  3. 如果任意值是true,则把true转换成1再进行比较;如果任意值是false,则把false转换成0再进行比较
  4. 如果一个是对象,另一个是数值或字符串,把对象转换成基础类型的值再比较
    • 对象转基础类型时,优先调用valueOf(),再调用toString()
    • 例外的是DateDate 利用的是toString()转换

经典题

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

//原因是 == 两边都转为数字进行比较,而不是 [] 转为布尔值与 false 比较

+ 运算符

+ 运算符可以作为一元运算符使用,此时的作用是将后边跟着的数据转为数字

+true // 1
+[] // 0
+new Date() //获取当前时间的Unix时间戳

在作为二元运算符使用时,+运算符比- * /运算符要复杂一些,因为其他的运算符都是处理数字的,而+运算符还可以处理字符串拼接。

  • 两边如果有字符串,另一边会转化为字符串进行相加
  • 如果没有字符串,两边都会转化为数字进行相加,对象也根据前面的方法转化为数字
  • 如果其中的一个操作数是对象,则将对象转换成原始值,日期对象会通过 toString()方法进行转换,其他对象通过valueOf()方法进行转换,但是大多数都是不具备可用的valueOf()方法,所以还是会通过toString()方法执行转换

简单来说就是,如果+运算符的其中一个操作数是字符串(或者通过以上步骤可以得到字符串),那么就执行字符串拼接,否则执行数字加法。

经典题

!+[]+[]+![] //"truefalse"

//首先第一个 + 左边不是数值,所以它是一元运算符,将后边跟着的 [] 转化为数字 0
//同时,最后一个 [] 左边是 ! 运算符,将 [] 转化为布尔值并取反,为 false
//转化后的结果为 !0 + [] + false
//!0 结果为 true,[] 转化为 "",所以结果变为 true + "" + false
//因为 第一个 + 右边有字符串,所以变为"true" + false
//最终结果为 "truefalse"

条件判断

以下条件判断的语句,会发生隐式转换为布尔值的情况:

  • if()语句中的条件判断表达式
  • for(..; ..; ..)语句中的条件判断表达式
  • while()do .. while()
  • ? :中的条件判断表达式
  • ||&&左边的操作数

补充:valueOf()和toString()

常用内置对象调用toString()valueOf()的返回情况

类型 toString valueOf
Object "[object 类型名]" 对象本身
String 字符串值 字符串值
Number 返回数值的字符串表示。还可返回以指定进制表示的字符串,默认10进制 数字值
Boolean "true" / "false" Boolean 值
Array 每个元素转换为字符串,用英文逗号作为分隔符进行拼接 数组本身
Date 日期的文本表示,格式为Wed Jun 05 2019 18:22:32 GMT+0800 (中国标准时间) 返回时间戳,等同于调用getTime()
Function 函数的文本表示 函数本身
RegExp 正则的文本表示 正则本身

以上是本篇文章的内容,欢迎大家提出自己的想法,我们一起学习进步,与君共勉。

参考资料