开胃菜
先说一个题外话,我在工作中遇到一个问题,需要比较 "08:00" 和 "09:00" 的大小,最后我找到三种方法:
- 在两个字符串前后各拼接相同的年月日和秒,拼成完整的时间格式进行比较:
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"
剩下的就不说了,比较两个完整的日期还是很容易的。
- 把两个字符串中的冒号去掉,转换成数字进行比较:
function timeToNumber(time) {
let [head,foot] = time.split(":")
return Number(head+foot)
}
var time1 = timeToNumber("08:00") //800
var time2 = timeToNumber("09:00") //900
- 直接比较
对,你没有看错,直接比较两个字符串:
"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]"
- 如果对象有
转布尔
- 所有的假值(
undefined、null、+0、-0、NaN、"")会被转化为false,其他都会被转为true - 所以,空对象、空数组都是
true
转对象
null和undefined转对象直接抛异常- 基本类型通过调用
String()、Number()、Boolean()构造函数,转换为他们各自的包装对象
使用场景
知道了各种数据类型转化的规则,那么在不同的场景中,究竟是怎么使用的呢?
== 运算符
常见的误区是:==检查值是否相等,===检查值和类型是否相等。
正确的解释是:==允许在相等比较中进行强制类型转换,而===不允许。
事实上,==和===都会检查操作数的类型,区别在于类型不同时它们的处理方式不同。
- 如果一个值是
null,另一个值是undefined,则相等 - 如果一个是字符串,另一个值是数字,则把字符串转换成数字,进行比较
- 如果任意值是
true,则把true转换成1再进行比较;如果任意值是false,则把false转换成0再进行比较 - 如果一个是对象,另一个是数值或字符串,把对象转换成基础类型的值再比较
- 对象转基础类型时,优先调用
valueOf(),再调用toString()。 - 例外的是
Date,Date利用的是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 | 正则的文本表示 | 正则本身 |
以上是本篇文章的内容,欢迎大家提出自己的想法,我们一起学习进步,与君共勉。