前言
今天再深入探究一个js基础知识——类型转换。我们从一个问题开始讲解:
|
|
大家可以试着将上面的代码粘贴到控制台,看看输出的是什么?请输完之后再看后面的内容。
我的天,出来的居然是”sb”!咳咳,如果以后你对谁不爽了,直接甩这堆符号过去吧。
言归正传,咱们来探究下为什么会输出”sb”吧。
“sb”是如何炼成的
其实这没有看上去那么深奥,也就是利用了一个很简单但又很强大的js的类型转化的原理。如果把js类型转化的原理搞清楚了,这些个符号真的不在话下。希望看完下面的讲解,你们能设法写出个”nb”来~
下面我们将这里用到的概念都先讲解一下
js运算符的优先级
下图列出了 JavaScript 运算符,并按优先级顺序从高到低排列。具有相同优先级的运算符按从左至右的顺序计算。
可以根据上图将上面的那段代码进行分解:
js类型转换
js的类型转换大致从两个方面讲:
- 操作符转换(比如一些单目运算:+, ++, –, if语句)
- 等号转换(比如:==, ===, >=, <…)。
上面的问题涉及到的是第一种转换。
原始值到原始值的转换
- 原始值转换为布尔值: undefined、null、0、-0、NaN、””被转为false;其他所有原始值都被转为true。
- 原始值转换成字符串:直接转换成字符串
- 原始值转换为数字:
- 布尔值转数字:true转换为1,false转换为0;
- 字符串转数字:空格被忽略;以数字表示的字符串可以直接转为数字,其他字符不会转为数字。
原始值对到对象的转换
null和undefined无法自动转换为对象,抛出TypeError异常。其他类型调用相应的构造函数,转为包装对象:字符串调用String()、数字调用Number()、布尔值调用Boolean()。
对象到原始值的转换
- 对象到布尔值:所有的对象都转为true
- 对象到字符串:如果对象有
toString()
方法,且调用toString()
返回原始值,则调用toString()
,并将返回的原始值转为字符串。否则,如果对象有valueOf()
方法,且调用valueOf()
返回原始值,则调用valueOf()
,并将返回的原始值转为字符串。否则,抛出类型错误异常 - 对象到数字:如果对象有valueOf()方法,且调用valueOf()返回原始值,则调用valueOf(),并将返回到原始值转为数字。否则,如果对象有toString()方法,且调用toString()返回原始值,则调用toString(),并将返回到原始值转为数字。否则,抛出类型错误异常
- toString()的转换规则:
- 数组类:每个元素转为一个字符串,并使用逗号连接。
- 函数类:通常是,将函数转换为JavaScript源代码字符串。
- 日期类:转换为一个日期和时间字符串(可被JavaScript解析)。
- RegExp类:转换为正则表达式直接量的字符串。
- valueOf()的转换规则:如果对象可以被转换为原始值,则转为原始值。否则,不做任何转换,返回对象本身。
- toString()的转换规则:
等号转换
严格操作符===
没有什么好讲的,也比较简单,以lRef == rRef
为例,只要Type(lRef)
与Type(rRef)
不同,那么总是会返回false
。否则,对象必须指向相同的对象引用,字符串必须包含相同的字符,其他原始类型必须拥有相同的值。
另外,null
和undefined
永远不会===
除自己以外其他类型,而NaN
不会与任何类型===
,甚至包括自己。
==
操作符的规则如下(还是以lRef == rRef
为例):
Type(x) | Type(y) | 结果 |
---|---|---|
x与y同类型 | - | 结果同严格判断操作符(===) |
null | Undefined | true |
Undefined | null | true |
Number | String | x == toNumber(y) |
String | Number | toNumber(x) == y |
Boolean | (any) | toNumber(x) == y |
(any) | Boolean | x == toNumber(y) |
String or Number | Object | x == toPrimitive(y) |
Object | String or Number | toPrimitive(x) == y |
otherwise… | - | false |
如果算法的结果是一个表达式,那么它将被重新计算,直到最后得到一个Primitive
的Boolean
值。
其中ToNumber
与ToPrimitive
跟ToBoolean
一样,为抽象方法,规则如下:
- ToNumber
参数类型 | 结果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | 如果为true,返回1,否则返回0 |
Number | 返回自身 |
String | 与调用Number(string)结果一致: “abc” -> NaN, “123” -> 123 |
Object | 会执行以下步骤:1. 让primValue = ToPrimitive(input argument, hint Number); 2. 返回ToNumber(primValue) |
- ToPrimitive
参数类型 | 结果 |
---|---|
Object | (在判等操作符的场景下)如果input.valueOf()返回一个原始类型(primitive),直接返回input.valueOf();否则,如果input.toString()返回一个原始类型,那么返回input.toString(); 否则报错。 |
otherwise… | 返回自身 |
规则都列出来了,那么下面咱们来分析下这段代码吧!
代码解析
我们一步步拆解这条语句,为了方便大家查找,我这里再贴一下代码:
|
|
先看这段(!(~+[])+{})
按照优先级,我们先执行+[]
, []
是一个对象类型,前面是个单目运算符+
,那么[]
肯定是要转换成Number
类型了。根据前面提到的:
对象到数字:如果对象有valueOf()方法,且调用valueOf()返回原始值,则调用valueOf(),并将返回到原始值转为数字。否则,如果对象有toString()方法,且调用toString()返回原始值,则调用toString(),并将返回到原始值转为数字。否则,抛出类型错误异常
因此首先调用[].valueOf()
方法,数组调用valueOf()
方法会返回自身,仍旧是个数组,因此不是原始值。所以就要调用toString()
方法,根据前面提到的数组类toString()
方法的转换规则:
每个元素转为一个字符串,并使用逗号连接。
因此,[].toString()
会返回一个空串""
,是原始值,于是调用Number("")
返回数字0
。至此,整个转换过程结束。+[]
转换成数字0
。接下来是~
,它是位运算符,作用可以记为把数字取负然后减一,所以~0
就是-1
。也就是说(~+[])
得到了-1
,然后取非,得到false
,至此,!(~+[])
的结果是false
。式子也就变成false + {}
,一个布尔值加上一个对象,那么对象{}先转化成原始类型,流程如下:
- 调用
toPrimitive
,发现是object
类型 - 调用
valueOf
,返回自身{}
- 不是原始类型,调用
toString
,返回[object Object]
false
与[object Object]
相加,false
先转化为字符串false
- 相加得结果
false[object Object]
因此,整个(!(~+[])+{})
返回的结果就是(false[object Object])
。
再看[–[~+””][+[]]*[~+[]] + ~~!+[]]
先看~+""
,这个很简单,因为""
之前有个加号+
,因此最终目的也是将""
转换成number
。而空串转换成number
会变成0
,因此+""
的结果就是0
,然后~0
的结果就是-1
。再看+[]
,这个之前分析过了就不再赘述,其结果就是0
,同样的~+[]
的结果就是-1
,~~!+[]
可以转换成~~!0
,而!0
的结果是true
,前面加个~
,会将true
转换成number
,也就是1
,然后~1
的值是-2
,再对-2
进行~
操作,就变成了1
。所以上式就变成了[--[-1][0]*[-1]+1]
。按照运算符的优先级,先执行[-1][0]
,得到结果是-1
,然后执行--
操作,得到-2
,然后执行-2*[-1]
,这里[-1]
会倾向于转换成number
类型,根据前面对象转初始值的转换规则,不难推出,其最终转换成-1
,因此-2*(-1)
变成2
,再加1
,变成3
,至此--[~+""][+[]]*[~+[]] + ~~!+[]
的结果就是3
。
整合(!(~+[])+{})[–[~+””][+[]]*[~+[]] + ~~!+[]]
经过前面的分析,不难得到,(!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]
经过一系列的转换,最终是这样子的式子: 'false[object Object]'[3]
,相信大家应该看出来了吧,就是取字符串'false[object Object]'
的第三个位置的字符,最后得到的当然是s
啦。至此sb
的s
已经推导结束。
({}+[])[[~!+[]]*~+[]]
相信经过上面的分析,这段也不难了。({}+[])
转换成'[object Object]'
,[~!+[]]*~+[]
的转换过程如下:
- ~!+[]变成-2
- ~+[]变成-1
- [-2]* (-1)结果是2
因此({}+[])[[~!+[]]*~+[]]
也就是'[object Object]'[2]
,当然就是b
啦。
‘s’和’b’相加,不就是’sb’么?
总结
前面总结了js运算符的优先级和js类型转换的一些原理。可以通过下面这两个题目来巩固下:
|
|
猜猜看这段代码会输出什么?
|
|
我不介意你把这段的结果写在评论里面的(坏笑脸)