前言
今天再深入探究一个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类型转换的一些原理。可以通过下面这两个题目来巩固下:
|
|
猜猜看这段代码会输出什么?
|
|
我不介意你把这段的结果写在评论里面的(坏笑脸)