一行神奇的JS代码,当时我就震惊了,这不就是传说中的ZB神奇么… … 哈哈。写本篇文章的缘由是之前看到了一段js代码,如下:
(!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]
然后让大家运行,出来的结果让人有点出乎意料,"sb"。太风骚了有木有!如果有人诋毁前端瞧不起JS的话,那就可以把这段代码发给他了~不过话说回来了,这到底是什么原理呢?为什么一堆符号运算结果竟然能是两个字符,而且恰巧还是个sb!
其实靠的是js的类型转化的一些基本原理,本篇就来揭密”sb”是如何炼成的。相信你如果能把这个理清楚了,以后遇到类型转化之类的题目,就可以瞬间秒杀了。
首先要运用到的第一个知识就是js运算符的优先级,因为这么长一段运算看的人眼花,我们必须得先根据优先级分成n小段,然后再各个击破。优先级的排列如下表,优先级从高到低:
根据此规则,我们把这一串运算分为以下16个子表达式:
运算符用红色标出,有一点可能大家会意识不到,其实中括号[]也是一个运算符,用来通过索引访问数组项,另外也可以访问字符串的子字符,有点类似charAt方法,如:’abcd'[1] // 返回’b’。而且中括号的优先级还是最高的哦。
二、JS的类型转化
预处理结束,接下来需要运用的就是javascript的类型转化知识了。我们先说说什么情况下需要进行类型转化。当操作符两边的操作数类型不一致或者不是基本类型(也叫原始类型)时,需要进行类型转化。先按运算符来分一下类:
减号-,乘号*,肯定是进行数学运算,所以操作数需转化为number类型。
加号+,可能是字符串拼接,也可能是数学运算,所以可能会转化为number或string
一元运算,如+[],只有一个操作数的,转化为number类型
下面来看一下转化规则。
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. 通过ToNumber()把值转换成Number,直接看ECMA 9.3的表格
规则如下:
参数 | 结果 |
Undefined | NaN |
Null | +0 |
Boolean | true被转换为1,false转换为+0 |
Number | 无需转换 |
String | 由字符串解析为数字。 |
3. 通过ToString()把值转化成字符串, 直接看ECMA 9.8的表格
规则如下:
参数 | 结果 |
Undefined | "undefined" |
Null | "null" |
Boolean | "true"或者"false" |
Number | 数字转换为字符串 |
String | 无需转换 |
规则就这么多,接下来实践一下,根据我们上面划分出的子表达式,一步一步将这个神奇的代码给执行出来。开工~
三、步步执行
先看最简单的子表达式16:+[]
只有一个操作数[],肯定是转化为number了,根据上面的规则2,[]是个数组,object类型,即对象。所以得先调用toPrimitive转化为原始类型,并且PreferredType为number,这个参数表示更“倾向于”转化的类型,这里肯定是number了。然后首先调用数组的valueOf方法,数组调用valueOf会返回自身,如下:
[].valueOf()
[]
这个时候,我们得到一个空串“”,还没有结束,看上面的规则2描述,继续调用toNumber,转化为number类型,如下:
Number("")
0
大功告成!子表达式16转化完毕,+[],最终得到0。来看子表达式15:[~+””]
空串””前面有两个一元操作符,但是操作数还是只有一个,所以,最终要转化为的类型是number。看规则2吧,空串调用toNumber得到0。接下来是~,这是个什么东东呢?它是位运算符,作用可以记为把数字取负然后减一,所以~0就是-1 。
别忘了,这个子表达式外头还包着中括号,所以最终的值为[-1],即一个数组,里面只有一个元素-1.
接下来看子表达式13就简单了,把15、16求出来的填进去,就变成了这样:–[-1][0],取数组的第0个元素,然后自减,结果为-2,是不so easy!
继续往上走,子表达式14: [~+[]]
其实把15、和16的原理用上就非常明显了,答案[-1]
继续来求子表达式9,此刻它已变成:-2*[-1],有稍许不一样,不过没关系,我们还是按照规则来,运算符是乘号*,当然是做数学运算,那后面的[-1]就得转化为number,与16的求法类似,过程如下:
1)调用toPrimitive,发现是object类型
2)调用valueOf,返回自身[-1]
3)因为不是原始类型,继续调用toString,返回”-1″
4)”-1″是原始类型了,然后调用toNumber,返回-1
5)与-2相乘,返回2
子表达式10:~~!+[],不多说了,答案1. 就是从右往左依次一元计算。
有了9和10,我们来到了子表达式4,此刻它已经长这样了:2+1, 好,我不多说了。
继续看表达式7:!(~+[]),~+[]=-1,这个根据上面已经知道了,那!-1是什么呢?这里要说一下这个感叹号,它是逻辑取非的意思,会把表达式转化为布尔类型,转化规则和js的Truthy和Falsy原则是一样的,后面跟数字的,除0以外都为false,后面跟字符串的,除空串以外都为false。这里的!-1当然就是false了。
接下来这个表达式3:false+{}有点关键。一个布尔加一个对象,那这个{}应该先转化为原始类型,流程如下:
1)调用toPrimitive,发现是object类型
2)调用valueOf,返回自身{},
3)不是原始类型,调用toString,返回”[object Object]”
4)false与”[object Object]”相加,false先转化为字符串”false”
5)相加得结果”false[object Object]”
知道了表达式3和4,我们就可以来看表达式1了,此时它是这样的:”false[object Object]”[3],因为这个[]可以取字符串的子字符,像charAt一样,所以得到了结果”s”
四、最后
经过上面艰难的流程,我们拿到了字符”s”,也就是那张图的左半边,剩下的那个”b”,相同的原理可以搞出来,我这里就不一一演示了,留给你练练吧~
回顾一下这个过程其实也不复杂,只是有一些需要重复劳动的,只要你掌握了运算的优先级,能把大串分解成一个个小串,然后运用类型转化的知识挨个处理就搞定了。怎么样,看到这里你还觉得神奇吗?
如果有人瞧不起JS,请把这段代码发给他,如果他想知道答案,请把本文发给他~
在写网页的程序的时候,经常碰到要在网页加载完全之后再去展现页面,加载中的时候通过显示loading...的样式。这时候我们会直接想到使用window.onload的方式,或者是img对象的complete属性
async/await 语法让异步调用写起来像写同步代码,在编写代码的时候,可以避免逻辑跳跃,写起来会更轻松。
使用UglifyJS合并/压缩JavaScript ,UglifyJS3与UglifyJS2相比API变动较大,简化较多,文档也增加了不少示例。
试着不用if撸代码,是件很有趣的事,而且,万一你领会了什么是“数据即代码,代码即数据”呢?
本文重在列出并解释说明 JS 中各种容易出错的坑和细节,供大家更加深入理解为什么 JS 会这样
学习JavaScript时,当时我对于undefined 和 null 比较困惑 ,因为他们都表示空值。他们有什么明确的区别吗?他们似乎都可以定义一个空值,而且 当你进行 在做null ===undefined 的比较时,结果是true。
JavaScript是一门伟大的语言,作为一门弱类型语言,它拥有非常简洁的语法,庞大的生态系统,灵活性非常强大。js各种神奇的写法,所谓的神奇也就是罕见。下面就开始介绍这些怪异的写法吧。
在前端开发中,遇到如下需求:隐藏手机号码,将中间几位替换为*。通过js如何实现手机号码隐藏中间4位呢?下面整理几种实现方式:使用正则、通过长度截取。
在一些网页中我们可以常见的“设置为首页”和“ 收藏本站”,以及“保存到桌面”等功能,使用js是如何实现的呢?这里为大家分享下实现方法,完美兼容IE,chrome,ff等浏览器
RGB和HSL是两种色彩空间,即:红,绿,蓝和色调,饱和度,亮度,前者适用于机器采样,目前的显示器颜色即由这三种基色构成,而后者更符合人类的直观感觉。这篇文章主要介绍原生js实现HSL与RGB相互转换
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!