原创禁止私自转载前端
部门长期招收大量研发岗位【前端,后端,算法】,欢迎各位大神投递,踊跃尝试。git
坐标: 头条,大量招人,难度有下降,大多能拿到很不错的涨幅,未上市,offer 给力!欢迎骚扰邮箱!程序员
戳我: 戳我: hooper.echo@gmail.comgithub
应该是腾讯面试题, 原题更加复杂
面试遇到这种使人头皮发麻的题,该怎么办呢? 不要慌,咱们科学的应对便可。面试
对于简短而罕见的写法,最好的方法就是经验法,基本原则就是瞎蒙,虽然听着有点扯淡,实际上这不失为一个好办法,对于一个比较陌生的问题,咱们经过经验瞎几把猜一个「大众」答案:算法
简单观察此题,咱们发现题目想让一个 数组和他的 非 做比较, 从正常的思惟来看,一个数和他的非,应该是不相等的。segmentfault
因此咱们 first An is : false
然而你看着面试官淫邪的笑容,忽然意识到,问题并不简单,毕竟这家公司还能够,不会来这么小儿科的问题吧。再转念一想,这 tm 的是 js 啊,毕竟 js 常常不按套路出牌啊。后端
因而你又大胆作出了一个假设: [] == ![] 是 true!
大体结论有了, 那该怎么推导这个结论呢?咱们逐步分解一下这个问题。分而治之数组
后面分析很长,涉及到大篇幅的 ECMAScript 规范的解读,冗长而枯燥,不想看的同窗,能够在这里直接拿到结论app
[] == ![]
->[] == false
->[] == 0
->[].valueOf() == 0
->[].toString() == 0
->'' == 0
->0 == 0
->true
若是你决定要看,千万坚持看完,三十分钟以后我必定会给你一个惊喜。
这是个奇怪的问题,乍一看形式上有些怪异, 若是面试中你遇到这么个题,应该会有些恼火:这 tm 什么玩意?! shift!(防和谐梗)。
虽然有点懵,不过仍是理性的分析一下,既然这个表达式含有多个运算符, 那首先仍是得看看运算符优先级。
运算符优先级表
而此题中出现了两个操做符: 「!」, 「==」, 查表可知, 逻辑非优先级是 16, 而等号优先级是 10, 可见先执行 ![]
操做。在此以前咱们先看看 逻辑非
逻辑运算符一般用于Boolean型(逻辑)值。这种状况,它们返回一个布尔型值。
语法描述: 逻辑非(!) !expr
js 中可以转换为false的字面量是可枚举的,包含
因此 ![] => false
因而乎咱们将问题转化为:
[] == false
这是个劲爆的操做符,正经功能没有,自带隐式类型转换常常使人对 js 另眼相看, 实际上如今网上也没有对这个操做符转换规则描述比较好的,这个时候咱们就须要去 ECMAscript 上去找找标准了。
ECMAScript® 2019 : 7.2.14 Abstract Equality Comparison
规范描述: The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
If Type(x) is the same as Type(y), then
依据规范 6, 7 可知,存在 bool 则会将自身 ToNumber 转换 !ToNumber(x) 参考 花絮下的 !ToNumber, 主要是讲解 !的意思 ! 前缀在最新规范中表示某个过程会按照既定的规则和预期的执行【一定会返回一个 number 类型的值,不会是其余类型,甚至 throw error】
获得: [] == !ToNumber(false)
ECMAScript® 2019 : 7.1.3ToNumber
If argument is true, return 1. If argument is false, return +0.
可知: !ToNumber(false) => 0; [] == 0
而后依据规范 8 9, 执行 ToPrimitive([])
ECMAScript® 2019 : 7.1.1ToPrimitive ( input [ , PreferredType ] )
The abstract operation ToPrimitive converts its input argument to a non-Object type. [尝试转换为原始对象]
If an object is capable of converting to more than one primitive type, it may use the optional hint PreferredType to favour that type. Conversion occurs according to the following algorithm. [若是一个对象能够被转换为多种原语类型, 则参考 PreferredType, 依据以下规则转换]
If Type(input) is Object, then
If exoticToPrim is not undefined, then
大体步骤就是 肯定 PreferredType 值[If hint is "default", set hint to "number".], 而后调用 GetMethod, 正常状况下 GetMethod 返回 GetV, GetV 将每一个属性值 ToObject, 而后返回 O.[[Get]](P, V).
ECMAScript® 2019 : 9.1.8[[Get]] ( P, Receiver )
Return the value of the property whose key is propertyKey from this object[检索对象的 propertyKey 属性值]
而后 ToPrimitive step 7 返回 OrdinaryToPrimitive(input, hint)
ECMAScript® 2019 : 7.1.1.1OrdinaryToPrimitive ( O, hint )
If hint is "string", then
Else,
For each name in methodNames in List order, do
5.2 If IsCallable(method) is true, then
上述过程说的很明白: 若是 hint is String,而且他的 value 是 string 或者 number【ToPrimitive 中给 hint 打的标签】,接下来的处理逻辑,3,4 步描述的已经很清楚了。
步骤 5,则是依次处理放入 methodNames 的操做[这也解答了我一直以来的一个疑问,网上也有说对象转 string 的时候,是调用 tostring 和 valueof, 可是老是含糊其辞,哪一个先调用,哪一个后调用,以及是否是两个方法都会调用等问题老是模棱两可,一句带过 /手动狗头]。
该了解的基本上都梳理出来了, 说实话,很是累,压着没有每一个名词都去发散。不过大体须要的环节都有了.
咱们回过头来看这个问题: 在对 == 操做符描述的步骤 8 9中,调用 ToPrimitive(y)
可见没指定 PreferredType, 所以 hint 是 default,也就是 number【参考: 7.1.1ToPrimitive 的步骤2-f】
接着调用 OrdinaryToPrimitive(o, number)
则进入 7.1.1.1OrdinaryToPrimitive 的步骤 4 ,而后进入步骤 5 先调用 valueOf,步骤 5.2.2 描述中若是返回的不是 Object 则直接返回,不然才会调用 toString。
因此 [] == 0
=> [].valueOf()[.toString()] == 0
. 咱们接着来看 数组的 valueOf 方法, 请注意区分一点,js 里内置对象都继承的到 valueOf 操做,可是部分对象作了覆写, 好比 String.prototype.valueOf,因此去看看 Array.prototype.valueOf 有没有覆写。
结果是没有,啪啪打脸啊,尼玛,因而乎咱们看 Object.prototype.valueOf
ECMAScript® 2019 : 19.1.3.7Object.prototype.valueOf ( )
When the valueOf method is called, the following steps are taken:
This function is the %ObjProto_valueOf% intrinsic object.
咱们接着看 ToObject【抓狂,可是要坚持】。
ECMAScript® 2019 : 7.1.13ToObject ( argument )
Object : Return argument?! 这步算是白走了。咱们接着看 toString,一样的咱们要考虑覆写的问题。
ECMAScript® 2019 : 22.1.3.28Array.prototype.toString ( )
可见调用了 join 方法【ps: 这里面还有个小故事,我曾经去滴滴面试,二面和我聊到这个问题,我说数组的 toString 调用了 join ,面试官给我说,你不要看着调用结果就臆测内部实现,不是这样思考问题的...... 我就摇了摇头,结果止步二面,猎头反馈的拒绝三连: 方向不匹配,不适合咱们,滚吧。😂 😂 😂 】
经过很是艰辛的努力咱们走到了这一步
[].valueOf().toString() == 0
=>[].join() == 0
=>'' == 0
若是你也认真看到这一步,不妨在博客提个 issue 留下联系方式,交个朋友 ^_^。
接着咱们看到两边仍是不一样类型,因此类型转换还得继续, 咱们回到 7.2.14 Abstract Equality Comparison 的步骤 4 5 ,
可见 '' 须要 ToNumber, 咱们在上面讲述了 ToNumber 以及转换映射表, 表格里说的很清楚『 String See grammar and conversion algorithm below. 』....
ECMAScript® 2019 : 7.1.3.1ToNumber Applied to the String Type
惋惜这一步描述的很是抽象
StringNumericLiteral::: StrWhiteSpaceopt StrWhiteSpaceoptStrNumericLiteralStrWhiteSpaceopt StrWhiteSpace::: StrWhiteSpaceCharStrWhiteSpaceopt StrWhiteSpaceChar::: WhiteSpace LineTerminator StrNumericLiteral::: StrDecimalLiteral BinaryIntegerLiteral OctalIntegerLiteral HexIntegerLiteral StrDecimalLiteral::: StrUnsignedDecimalLiteral +StrUnsignedDecimalLiteral -StrUnsignedDecimalLiteral StrUnsignedDecimalLiteral::: Infinity DecimalDigits.DecimalDigitsoptExponentPartopt .DecimalDigitsExponentPartopt DecimalDigitsExponentPartopt
具体分解以下:
ECMAScript® 2019 : 11.8.3Numeric Literals
摘录一点咱们须要用的:
... DecimalIntegerLiteral:: 0 NonZeroDigitDecimalDigitsopt
确认过眼神,是我搞不定的人!整个过程大体描述的是
不过咱们还有 Mozilla : Number("") // 0
因此最终答案就转化为:
'' == 0
=>0 == 0
哦,大哥,原来这 tm 就是惊喜啊!小弟我愿意... 愿意个鬼啊!
true
可是若是博客就写这四点的话, 那你看完仍是知其然不知其因此然。 因此我就写的比较详细。同时也是由于网上不少博客在 valueOf 和 toString 的调用上(顺序,和为何两个都调用)老是说不清楚,转载几回就开始乱写了, 还有==的转换规则上,都是含糊其辞,因此我就想吧这个问题搞得明明白白。
若是你也有问题, 请点开 issue ,加上去吧,不想踩坑的技术能够提上去,问题也能够提上去。
ECMAScript® 2019 : 5.2.3.4 ReturnIfAbrupt Shorthands
Similarly, prefix ! is used to indicate that the following invocation of an abstract or syntax-directed operation will never return an abrupt completion[The term “abrupt completion” refers to any completion with a [[Type]] value other than normal.] and that the resulting Completion Record's [[Value]] field should be used in place of the return value of the operation. For example, the step:
is equivalent to the following steps:
Syntax-directed operations for runtime semantics make use of this shorthand by placing ! or ? before the invocation of the operation:
大意是: !后面的语法操做的调用永远不会返回忽然的完成,我理解是必定会执行一个预期的结果类型,执行步骤就是 上述 1, 2, 3步骤。 !ToNumber 描述的是 必定会讲操做数转换为 number 类型并返回 val.[[value]]
同理本身看规范, 就不一一展开了,太多「逃」。
ECMAScript® 2019 : 5.2.3.4 ReturnIfAbrupt Shorthands
Invocations of abstract operations and syntax-directed operations that are prefixed by ? indicate that ReturnIfAbrupt should be applied to the resulting Completion Record.