0. 前言
JavaScript是弱类型语言,在定义变量时不需要明确定义类型,刚接触是觉得非常灵活,很方便。
但随着开发走进深水区,更多的协作开发,越发觉得这种弱类型语言不受控,容易出错。
社区为了解决弱类型的问题,也有了如Flow、TypeScript等扩充。从团队的收益出发,增加这些是一个好的选择,但是也增加了许多学习成本。思考本质的话,这其实是为了解决问题而引入新的语法糖,增加了项目的复杂度,将问题转移罢了。
因此对于个人成长而言,对待问题的最好解决办法是正视它,剖析其背后原理。
本文将尝试将JavaScript的类型转换归纳总结,试图将日常开发遇到的问题与技巧尽可能地分享给大家。
原始数据类型:boolean、number、bigint、string、undefined、null、symbol(ES2016新增)
复合数据类型:Object
1. 相等算法
截止ES2015,存在4种数值相等算法:
- Abstract Equality Comparison(==):也称半等。
- Strict Equality Comparison(===):也成全等;使用相同算法的还有
Array.prototype.indexOf、Array.prototype.lastIndexOf、case语法 - Same-Value-Zero:
Set、Map、还有Array.prototype.includes和String.prototype.includes - Same-Value:除了以上都是该算法;常见:
Object.is
半等和全等比较常见,唯一差别就是是否进行类型转换(当类型相同时,两个算法是相等的)
半等和全等为了满足IEEE 754标准,做了特殊处理:NaN ! = NaN 和 -0 == +0
1.1 半等 Abstract Equality Comparison
半等(==)的逻辑大致可以这么理解:
-
类型是否相等?同类型则使用全等比较,类型不同则往下
-
两个数分别是undefined、null?是则返回true,否则往下
-
类型是boolean或string?通过转换成number再比较
使用
Number()转换而不是parseInt,可以看下面代码:'123abc' == 123 // false parseInt('123abc') == 123 // true -
类型是object?通过**转换成原始数据类型(
toPrimitive)**再比较转换原始数据类型优先使用
valueOf()而不是toString(),但如果未定义valueOf()则会使用toSting()var obj = { valueOf: () => 1, toString: () => 2 } obj == 1 // true obj == 2 // false var obj2 = { toString: () => 2 } obj == 2 // true -
都不是则返回false
总的原则是:尽可能都转换成Number容易比较
1.2 全等 Strict Equality Comparison
全等(===)的逻辑比较符合预期:
- 类型不一致,直接返回false
- 如果是undefined或null,则返回true
- 如果是Number的话,做了特殊处理:
NaN != NaN-0 == +0
- 如果是对象,查看是否是同个引用,是则返回true,否则返回false
- 剩下的逻辑都是判断两值是否相等
1.3 Same-Value算法
之所以会提供这个算法,是因为半等和全等存在两个问题:
- 无法正确判断NaN,因为
NaN != NaN - 无法区分
+0和-0
而支持该算法的函数有Object.is() :
Object.is(-0, +0) // false
-0 == +0 // true
-0 === +0 // true
Object.is(NaN, NaN) // true
NaN == NaN // false
NaN === NaN // false
此处NaN的判断又可以延伸一下,ES2015提供了Number.isNaN方法
此时,你会很奇怪,之前不是有个全局方法isNaN了吗?是的,这个方法也是很诡异的。
因为isNaN会对传入的参数做类型转换,如果能转换成Number类型,则返回true,否则返回false
而Number.isNaN修正了这个逻辑,只做纯粹的判断,避免了歧义:
var numberStr = '123'
isNaN(numberStr) // false
Number.isNaN(numberStr) // false
var str = 'abc'
isNaN(str) // true **歧义点**
Number.isNaN(str) // false
isNaN(NaN) // true
Number.isNaN(NaN) // true
因此,Number.isNaN的pollfill可以这样实现:
Number.isNaN = function(value) {
return typeof value == 'number' && isNaN(value);
}
1.4 Same-Value-Zero算法
很多时候,可能并不想区别开+0与-0,但还需要知道是不是NaN,因此推出了这个算法。
这个算法和Same-Value算法很类似,以为差别是目前这个算法 +0 等于 -0 。
内置该算法的函数前面有提到,下面以Set举例:
let set = new Set()
set.add(NaN)
set.add(0 / 0) // 0 / 0 = NaN 因此不会添加
console.log(set.size) // 1
set.add(+0)
set.add(-0) // -0 == +0 因此不会添加
console.log(set.size) // 2
参考资料: