前端进阶 - 类型转换

199 阅读4分钟

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.indexOfArray.prototype.lastIndexOfcase语法
  • Same-Value-ZeroSetMap、还有Array.prototype.includesString.prototype.includes
  • Same-Value:除了以上都是该算法;常见:Object.is

半等和全等比较常见,唯一差别就是是否进行类型转换(当类型相同时,两个算法是相等的)

半等和全等为了满足IEEE 754标准,做了特殊处理:NaN ! = NaN 和 -0 == +0

1.1 半等 Abstract Equality Comparison

半等(==)的逻辑大致可以这么理解:

  1. 类型是否相等?同类型则使用全等比较,类型不同则往下

  2. 两个数分别是undefined、null?是则返回true,否则往下

  3. 类型是boolean或string?通过转换成number再比较

    使用Number()转换而不是parseInt,可以看下面代码:

    '123abc' == 123 // false
    parseInt('123abc') == 123 // true
    
  4. 类型是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
    
  5. 都不是则返回false

总的原则是:尽可能都转换成Number容易比较

1.2 全等 Strict Equality Comparison

全等(===)的逻辑比较符合预期:

  1. 类型不一致,直接返回false
  2. 如果是undefined或null,则返回true
  3. 如果是Number的话,做了特殊处理:
    • NaN != NaN
    • -0 == +0
  4. 如果是对象,查看是否是同个引用,是则返回true,否则返回false
  5. 剩下的逻辑都是判断两值是否相等

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

参考资料: