共计 3295 个字符,预计需要花费 9 分钟才能阅读完成。
前言
很多小伙伴们觉得 javaScript 很简单, 下面的这行 javaScript 代码可能会让你怀疑人生。
(!(~+[])+{})[–[~+””][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]
小伙伴们学会了,以后遇到一些讲不通道理, 让你不开森的人就把这段代码发给他。
那么这段代码为什么会输出 sb 呢?
其实这段代码考的是 js 的类型转化的一些基本原理
首先要运用到的第一个知识就是 js 运算符的优先级,因为这么长一段运算看的人眼花,我们必须得先根据优先级分成 n 小段,然后再各个击破
1, js 运算符的优先级
优先级的排列如下表:
优先级从高到低:
根据此规则,我们把这一串运算分为以下 16 个子表达式:
运算符用红色标出,有一点可能大家会意识不到,其实中括号 [] 也是一个运算符,用来通过索引访问数组项,另外也可以访问字符串的子字符,有点类似 charAt 方法,如:’abcd'[1] // 返回 ’b’。而且中括号的优先级还是最高的哦。
接下来需要运用的就是 javascript 的类型转化知识了, 我们先说说什么情况下需要进行类型转化。当操作符两边的操作数类型不一致或者不是基本类型(也叫原始类型)时,需要进行类型转化。
让我们快速的复习一下. 在 JavaScript 中, 一共有两种类型的值: 原始值 (primitives) 和对象值(objects).
原始值有:undefined, null, 布尔值(booleans), 数字(numbers), 还有字符串(strings).
其他的所有值都是对象类型的值, 包括数组 (arrays) 和函数(functions).
2. 类型转化
(1)先按运算符来分一下类:
减号 -,乘号 *,肯定是进行数学运算,所以操作数需转化为 number 类型。
加号 +,可能是字符串拼接,也可能是数学运算,所以可能会转化为 number 或 string
一元运算,如 +[],只有一个操作数的,转化为 number 类型
(2)下面来看一下转化规则。
(2).1 对于非原始类型的,通过 ToPrimitive() 将值转换成原始类型:
ToPrimitive(input, PreferredType?)
可选参数 PreferredType 是 Number 或者是 String。返回值为任何原始值. 如果 PreferredType 是 Number, 执行顺序如下:
如果 input 为 primitive,返回
否则,input 为 Object。调用 obj.valueOf()。如果结果是 primitive, 返回。
否则,调用 obj.toString(). 如果结果是 primitive, 返回
否则,抛出 TypeError
如果 PreferredType 是 String,步骤 2 跟 3 互换,如果 PreferredType 没有,Date 实例被设置成 String,其他都是 Number
(2).2 通过 ToNumber()将值转换为数字
通过 ToNumber()把值转换成 Number,直接看 ECMA 9.3 的表格
参数
结果
undefined
NaN
null
+0
布尔值
true 被转换为 1,false 转换为 +0
数字
无需转换
字符串
由字符串解析为数字. 例如,”324″ 被转换为 324
如果输入的值是一个对象, 则会首先会调用 ToPrimitive(obj, Number)将该对象转换为原始值, 然后在调用 ToNumber()将这个原始值转换为数字.
(2).3 通过 ToString()将值转换为字符串
通过 ToString()把值转化成字符串,直接看 ECMA 9.8 的表格
参数
结果
undefined
“undefined”
null
“null”
布尔值
“true” 或者 “false”
数字
数字作为字符串, 比如. “1.765”
字符串
无需转换
如果输入的值是一个对象, 则会首先会调用 ToPrimitive(obj, String)将该对象转换为原始值, 然后再调用 ToString()将这个原始值转换为字符串.
规则就这么多,接下来实践一下,根据我们上面划分出的子表达式,一步一步将这个神奇的代码给执行出来。开工~
先看最简单的子表达式 16:+[]
只有一个操作数 [],肯定是转化为 number 了,根据上面的规则 2,[] 是个数组,object 类型,即对象。所以得先调用 toPrimitive 转化为原始类型,并且 PreferredType 为 number,这个参数表示更“倾向于”转化的类型,这里肯定是 number 了。然后首先调用数组的 valueOf 方法,数组调用 valueOf 会返回自身,如下:这个时候,我们得到一个空串“”,还没有结束,看上面的规则 2 描述,继续调用 toNumber,转化为 number 类型,如下:
大功告成!子表达式 16 转化完毕,+[],最终得到 0。
来看子表达式 15:[~+””]
空串 ”” 前面有两个一元操作符,但是操作数还是只有一个,所以,最终要转化为的类型是 number。看规则 2 吧,空串调用 toNumber 得到 0。接下来是~,这是个什么东东呢?它是位运算符,作用可以记为把数字取负然后减一,所以~0 就是 -1。
别忘了,这个子表达式外头还包着中括号,所以最终的值为[-1],即一个数组,里面只有一个元素 -1.
接下来看子表达式 13 就简单了,把 15、16 求出来的填进去,就变成了这样:—1,取数组的第 0 个元素,然后自减,结果为 -2,是不 so easy!
继续往上走,子表达式 14:[~+[]]
其实把 15、和 16 的原理用上就非常明显了,答案[-1]
继续来求子表达式 9,此刻它已变成:-2[-1],有稍许不一样,不过没关系,我们还是按照规则来,运算符是乘号,当然是做数学运算,那后面的 [-1] 就得转化为 number,与 16 的求法类似,过程如下:
①调用 toPrimitive,发现是 object 类型
②调用 valueOf,返回自身[-1]
③因为不是原始类型,继续调用 toString,返回 ”-1″
④”-1″ 是原始类型了,然后调用 toNumber,返回 -1
⑤与 - 2 相乘,返回 2
子表达式 10:~~!+[],不多说了,答案 1. 就是从右往左依次一元计算。
有了 9 和 10,我们来到了子表达式 4,此刻它已经长这样了:2+1,好,我不多说了。
继续看表达式 7:!(~+[]),~+[]=-1,这个根据上面已经知道了,那!- 1 是什么呢?这里要说一下这个感叹号,它是逻辑取非的意思,会把表达式转化为布尔类型,转化规则和 js 的 Truthy 和 Falsy 原则是一样的,后面跟数字的,除 0 以外都为 false,后面跟字符串的,除空串以外都为 false。这里的!- 1 当然就是 false 了。
接下来这个表达式 3:false+{}有点关键。一个布尔加一个对象,那这个 {} 应该先转化为原始类型,流程如下:
①调用 toPrimitive,发现是 object 类型
②调用 valueOf,返回自身{},③不是原始类型,调用 toString,返回 ”[object Object]”
④false 与 ”[object Object]” 相加,false 先转化为字符串 ”false”
⑤相加得结果 ”false[object Object]”
知道了表达式 3 和 4,我们就可以来看表达式 1 了,此时它是这样的:”false[object Object]”[3],因为这个 [] 可以取字符串的子字符,像 charAt 一样,所以得到了结果 ”s”
经过上面艰难的流程,我们拿到了字符 ”s”,也就是那张图的左半边,剩下的那个 ”b”,相同的原理可以搞出来,我这里就不一一演示了,留给你练练吧~
回顾一下这个过程其实也不复杂,只是有一些需要重复劳动的,只要你掌握了运算的优先级,能把大串分解成一个个小串,然后运用类型转化的知识挨个处理就搞定了。怎么样,看到这里你还觉得神奇吗?
同样的,中文字符也是由这样组成的,跟英文同样的道理。
参考
https://www.cnblogs.com/ziyun…https://zhidao.baidu.com/ques…