资讯专栏INFORMATION COLUMN

前端面试中遇到 [] == ![] ? 刨祖坟式博客解析,从 ECMAScript 规范说起,比脱下

codeGoogle / 2768人阅读

摘要:这种情况,它们返回一个布尔型值。语法描述逻辑非如果能转换为,返回如果能转换为,则返回。转中能够转换为的字面量是可枚举的,包含空字符串。

博客 github 地址: https://github.com/HCThink/h-blog/blob/master/interesting/in5.md

github 首页(star+watch,一手动态直达): https://github.com/HCThink/h-blog

掘金 link , 掘金 专栏

segmentfault 主页

原创禁止私自转载

广告

部门长期招收大量研发岗位【前端,后端,算法】,欢迎各位大神投递,踊跃尝试。

坐标: 头条,大量招人,难度有降低,大多能拿到很不错的涨幅,未上市,offer 给力!欢迎骚扰邮箱!

戳我: 戳我: hooper.echo@gmail.com

[] == ![] ?
应该是腾讯面试题, 原题更加复杂

面试遇到这种令人头皮发麻的题,该怎么办呢? 不要慌,我们科学的应对即可。

经验法,简称瞎蒙

对于简短而罕见的写法,最好的方法就是经验法,基本原则就是瞎蒙,虽然听着有点扯淡,实际上这不失为一个好办法,对于一个比较陌生的问题,我们通过经验瞎几把猜一个「大众」答案:

简单观察此题,我们发现题目想让一个 数组和他的 非 作比较, 从正常的思维来看,一个数和他的非,应该是不相等的。

所以我们 first An is : false
反向操作法

然而你看着面试官淫邪的笑容,突然意识到,问题并不简单,毕竟这家公司还可以,不会来这么小儿科的问题吧。再转念一想,这 tm 的是 js 啊,毕竟 js 经常不按套路出牌啊。

于是你又大胆做出了一个假设: [] == ![] 是 true!

大致结论有了, 那该怎么推导这个结论呢?我们逐步分解一下这个问题。分而治之

最终结论

后面分析很长,涉及到大篇幅的 ECMAScript 规范的解读,冗长而枯燥,不想看的同学,可以在这里直接拿到结论

[] == ![] -> [] == false -> [] == 0 -> [].valueOf() == 0 -> [].toString() == 0 -> "" == 0 -> 0 == 0 -> true
分析

如果你决定要看,千万坚持看完,三十分钟之后我一定会给你一个惊喜。

这是个奇怪的问题,乍一看形式上有些怪异, 如果面试中你遇到这么个题,应该会有些恼火:这 tm 什么玩意?! shift!(防和谐梗)。

虽然有点懵,不过还是理性的分析一下,既然这个表达式含有多个运算符, 那首先还是得看看运算符优先级。

运算符优先级
运算符优先级表

而此题中出现了两个操作符: 「!」, 「==」, 查表可知, 逻辑非优先级是 16, 而等号优先级是 10, 可见先执行 ![] 操作。在此之前我们先看看 逻辑非

逻辑非 !

mozilla 逻辑非: !

逻辑运算符通常用于Boolean型(逻辑)值。这种情况,它们返回一个布尔型值。

语法描述: 逻辑非(!) !expr

如果expr能转换为true,返回false;

如果expr能转换为false,则返回true。

转 bool

js 中能够转换为false的字面量是可枚举的,包含

null;

NaN;

0;

空字符串("");

undefined。

所以 ![] => 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

Return the result of performing Strict Equality Comparison x === y.

If x is null and y is undefined, return true.

If x is undefined and y is null, return true.

If Type(x) is Number and Type(y) is String, return the result of the comparison x == ! ToNumber(y).

If Type(x) is String and Type(y) is Number, return the result of the comparison ! ToNumber(x) == y.

If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y.

If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y).

If Type(x) is either String, Number, or Symbol and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).

If Type(x) is Object and Type(y) is either String, Number, or Symbol, return the result of the comparison ToPrimitive(x) == y.

Return false.

依据规范 6, 7 可知,存在 bool 则会将自身 ToNumber 转换 !ToNumber(x) 参考 花絮下的 !ToNumber, 主要是讲解 !的意思 ! 前缀在最新规范中表示某个过程会按照既定的规则和预期的执行【必定会返回一个 number 类型的值,不会是其他类型,甚至 throw error】

得到: [] == !ToNumber(false)

ToNumber

ECMAScript® 2019 : 7.1.3ToNumber

If argument is true, return 1. If argument is false, return +0.

可知: !ToNumber(false) => 0; [] == 0

然后依据规范 8 9, 执行 ToPrimitive([])

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, 依据如下规则转换]

Assert: input is an ECMAScript language value.

If Type(input) is Object, then

If PreferredType is not present, let hint be "default".

Else if PreferredType is hint String, let hint be "string".

Else PreferredType is hint Number, let hint be "number".

Let exoticToPrim be ? GetMethod(input, @@toPrimitive).

If exoticToPrim is not undefined, then

Let result be ? Call(exoticToPrim, input, « hint »).

If Type(result) is not Object, return result.

Throw a TypeError exception.

If hint is "default", set hint to "number".

Return ? OrdinaryToPrimitive(input, hint).

Return input.

大致步骤就是 确定 PreferredType 值[If hint is "default", set hint to "number".], 然后调用 GetMethod, 正常情况下 GetMethod 返回 GetV, GetV 将每个属性值 ToObject, 然后返回 O.[[Get]](P, V).

Assert: IsPropertyKey(P) is true.

Let O be ? ToObject(V).

Return ? O.[[Get]](P, V).

[[Get]]

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)

OrdinaryToPrimitive( O, hint )

ECMAScript® 2019 : 7.1.1.1OrdinaryToPrimitive ( O, hint )

Assert: Type(O) is Object.

Assert: Type(hint) is String and its value is either "string" or "number".

If hint is "string", then

Let methodNames be « "toString", "valueOf" ».

Else,

Let methodNames be « "valueOf", "toString" ».

For each name in methodNames in List order, do

5.1 Let method be ? Get(O, name).

5.2 If IsCallable(method) is true, then

5.2.1 Let result be ? Call(method, O).

5.2.2 If Type(result) is not Object, return result.

Throw a TypeError exception.

上述过程说的很明白: 如果 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

Array.prototype.valueOf from Object.prototype.valueOf

ECMAScript® 2019 : 19.1.3.7Object.prototype.valueOf ( )

When the valueOf method is called, the following steps are taken:

Return ? ToObject(this value).

This function is the %ObjProto_valueOf% intrinsic object.

我们接着看 ToObject【抓狂,但是要坚持】。

ToObject

ECMAScript® 2019 : 7.1.13ToObject ( argument )

Object : Return argument?! 这步算是白走了。我们接着看 toString,同样的我们要考虑覆写的问题。

Array.prototype.toString()

ECMAScript® 2019 : 22.1.3.28Array.prototype.toString ( )

Let array be ? ToObject(this value).

Let func be ? Get(array, "join").

If IsCallable(func) is false, set func to the intrinsic function %ObjProto_toString%.

Return ? Call(func, array).

可见调用了 join 方法【ps: 这里面还有个小故事,我曾经去滴滴面试,二面和我聊到这个问题,我说数组的 toString 调用了 join ,面试官给我说,你不要看着调用结果就臆测内部实现,不是这样思考问题的...... 我就摇了摇头,结果止步二面,猎头反馈的拒绝三连: 方向不匹配,不适合我们,滚吧。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99965.html

相关文章

  • JavaScript系列(四) - 收藏集 - 掘金

    摘要:函数式编程前端掘金引言面向对象编程一直以来都是中的主导范式。函数式编程是一种强调减少对程序外部状态产生改变的方式。 JavaScript 函数式编程 - 前端 - 掘金引言 面向对象编程一直以来都是JavaScript中的主导范式。JavaScript作为一门多范式编程语言,然而,近几年,函数式编程越来越多得受到开发者的青睐。函数式编程是一种强调减少对程序外部状态产生改变的方式。因此,...

    cfanr 评论0 收藏0
  • 校招社招必备核心前端面试问题与详细解答

    摘要:本文总结了前端老司机经常问题的一些问题并结合个人总结给出了比较详尽的答案。网易阿里腾讯校招社招必备知识点。此外还有网络线程,定时器任务线程,文件系统处理线程等等。线程核心是引擎。主线程和工作线程之间的通知机制叫做事件循环。 showImg(https://segmentfault.com/img/bVbu4aB?w=300&h=208); 本文总结了前端老司机经常问题的一些问题并结合个...

    jonh_felix 评论0 收藏0
  • 校招社招必备核心前端面试问题与详细解答

    摘要:本文总结了前端老司机经常问题的一些问题并结合个人总结给出了比较详尽的答案。网易阿里腾讯校招社招必备知识点。此外还有网络线程,定时器任务线程,文件系统处理线程等等。线程核心是引擎。主线程和工作线程之间的通知机制叫做事件循环。 showImg(https://segmentfault.com/img/bVbu4aB?w=300&h=208); 本文总结了前端老司机经常问题的一些问题并结合个...

    Rango 评论0 收藏0
  • ++[[]][+[]]+[+[]]==10? 深入浅出弱类型 JS 的隐转换

    摘要:与此相对,强类型语言的类型之间不一定有隐式转换。三为什么是弱类型弱类型相对于强类型来说类型检查更不严格,比如说允许变量类型的隐式转换,允许强制类型转换等等。在中,加性运算符有大量的特殊行为。 从++[[]][+[]]+[+[]]==10?深入浅出弱类型JS的隐式转换 本文纯属原创? 如有雷同? 纯属抄袭? 不甚荣幸! 欢迎转载! 原文收录在【我的GitHub博客】,觉得本文写的不算烂的...

    miya 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0

发表评论

0条评论

codeGoogle

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<