[]==![]?应该是腾讯面试题,原题比较复杂。面试遇到这种头皮发麻的问题怎么办?不要惊慌,我们可以科学应对。经验法,简称盲法。对于简短而罕见的写作,最好的方法是经验法。基本原则是盲目性。虽然听起来有点废话,但其实是个好办法。对于一个比较陌生的问题,我们根据经验来猜一个“通俗”的答案:简单观察这道题,我们发现这道题想要比较一个数组及其否定。从正常的思维角度来看,一个数和它的负数不应该相等。所以我们首先An的是:false逆向操作方法然而,当你看着面试官淫秽的笑容时,你突然意识到问题并不简单。毕竟这家公司还好,不会有这种小问题。再想想,这tm就是js。毕竟js经常不按套路出牌。所以你又做了一个大胆的假设:[]==![]是真的!既然我们有了一个大概的结论,那我们应该如何推导出这个结论呢?让我们一步一步地分解这个。分而治之的最终结论在分析之后很长,涉及到对大型ECMAScript规范的解读,冗长乏味。不想看的同学可以在这里直接得出结论[]==![]->[]==false->[]==0->[].valueOf()==0->[].toString()==0->''==0->0==0->true解析如果你决定要看,一定要继续看下去,我一定会在30分钟后给你一个惊喜。这是一个奇怪的问题。乍一看,它的形式看起来有点奇怪。如果在面试中遇到这样的问题,你应该有点恼火:这tm是什么鬼?!转移!(反和谐梗)。虽然有点迷糊,但还是理性分析一下。由于这个表达式包含多个运算符,我们首先要看运算符的优先级。运算符优先级运算符优先级表本题中有两个运算符:“!”、“==”。从表中可以看出,逻辑非优先级为16,等号优先级为10。可见,首先执行了![]操作。在我们这样做之前,让我们看一下逻辑非逻辑非!mozilla逻辑非:!逻辑运算符通常用于布尔类型(逻辑)值。在这种情况下,它们返回一个布尔值。语法说明:LogicalNOT(!)!expr如果expr可以转换为true,则返回false;如果expr可以转换为false,则返回true。booljs中可以转为false的字面量是可枚举的,包括null;南;0;空字符串("");不明确的。所以![]=>false于是我们把问题变成了:[]==false==operator这是一个强大的运算符,它没有什么正经的功能,而且它自带的隐式类型转换常常让人对js刮目相看。事实上,现在网上并没有很好的描述运算符转换规则。这时候我们就需要去ECMAscript里面找标准了。ECMAScript?2019:7.2.14AbstractEqualityComparison规范描述:比较x==y,其中x和y是值,产生true或false。这样的比较执行如下:如果Type(x)与Type(y)相同,则返回执行严格相等比较的结果x===y。如果x为null且y未定义,则返回true。如果x未定义且y为null,返回true。如果Type(x)为Number,Type(y)为String,则返回比较结果x==!ToNumber(y).如果Type(x)是String,Type(y)是Number,返回比较结果!ToNumber(x)==y.IfType(x)isBoolean,returntheresultofthecomparison!ToNumber(x)==y。如果Type(y)为Boolean,则返回比较结果x==!ToNumber(y)。如果Type(x)是String、Number或Symbol且Type(y)是Object,则返回比较结果x==ToPrimitive(y)。如果Type(x)是Object且Type(y)为String、Number或Symbol,返回ToP比较结果原语(x)==y.返回假。根据规范6和7,如果有bool,它会将自己转换为ToNumber!ToNumber(x)指的是花絮下!ToNumber,主要是解释一下!意思!最新规范中的前缀表示某个流程将按照既定的规则和期望执行[必须返回一个number类型的值,不能是其他类型,甚至会抛出错误]get:[]==!ToNumber(false)ToNumberECMAScript?2019:7.1.3ToNumber如果参数为真,则返回1。如果参数为假,则返回+0。可以看出:!ToNumber(false)=>0;[]==0然后执行ToPrimitive([])ToPrimitiveECMAScript?2019:7.1.1ToPrimitive(input[,PreferredType])抽象操作ToPrimitive将其输入参数转换为非根据规范89的对象类型。[尝试转换为原始对象]如果一个对象能够转换为多个原始类型,它可以使用可选的提示PreferredType来支持该类型。转换根据以下算法发生。[如果一个对象可以被转换成多种原始类型,参考PreferredType,按照以下规则进行转换]Assert:inputisanECMAScriptlanguagevalue.IfType(input)isObject,thenIfPreferredTypeisnotpresent,lethint是“默认”。否则,如果PreferredType是提示字符串,让提示为“字符串”。否则PreferredType为提示数字,让提示为“数字”。让exoticToPrim为?GetMethod(input,@@toPrimitive)。如果exoticToPrim不是未定义的,则让结果为“默认”,将提示设置为“数字”。返回?OrdinaryToPrimitive(input,hint).returninput.大致步骤是确定PreferredType的值[如果hint是“default”,设置hint为“number”],然后调用GetMethod,一般情况下GetMethod返回GetV,GetV会每个属性值ToObject,然后返回O.[[Get]](P,V)。断言:IsPropertyKey(P)为真。让O成为?ToObject(V).返回?O.[[Get]](P,V).[[Get]]ECMAScript?2019:9.1。8[[Get]](P,Receiver)从这个对象返回key为propertyKey的属性的值[获取对象的propertyKey属性值]然后ToPrimitivestep7returnOrdinaryToPrimitive(input,hint)OrdinaryToPrimitive(O,hint)ECMAScript?2019:7.1.1.1OrdinaryToPrimitive(O,hint)Assert:Type(O)isObject.Assert:Type(hint)是String,它的值是“string”或“number”。如果hint是“string”,那么让methodNames是?“toString”,“valueOf”?。否则,令methodNames为?"valueOf","toString"?。对于列表顺序中methodNames中的每个名称,do5.1令method为?Get(O,name).5.2如果IsCallable(method)为真,则5.2.1让结果为?Call(method,O).5.2.2如果Type(result)不是Object,返回result。抛出TypeError异常。上面的过程已经很清楚了:如果hint是String,它的值是string或者number【ToPrimitive中hint的标签】,接下来的处理逻辑,步骤3和4的描述已经很清楚了。第5步,依次处理放methodNames的操作【这也解答了我一直以来的一个疑问,网上也有对象转字符串的时候,就是调用tostring和valueof,但是总是模棱两可的,先调用哪个,后调用哪个,两种方法是否都会调用,总是模棱两可的。基本把需要理解的都整理出来了。老实说,我很累,我没有每个名词可以发散。但大致必要的链接都在那里。我们回过头来看这个问题:==运算符描述的第8步和第9步,调用ToPrimitive(y)显示没有指定PreferredType,所以hint是default,也就是number[参考:Step2-fof7.1.1ToPrimitive]然后调用OrdinaryToPrimitive(o,number)进入7.1.1.1OrdinaryToPrimitive的step4,然后进入step5先调用valueOf,如果return不是step5.2.2描述的Object则直接返回,否则toString被调用。所以[]==0=>[].valueOf()[.toString()]==0接下来我们看数组的valueOf方法。请注意区别。js中内置的对象都继承了valueOf操作,但是有些对象已经被覆盖了,比如String.prototype.valueOf,所以查看Array.prototype.valueOf是否被覆盖。结果是没有,打脸,妈的,所以我们从Object.prototype.valueOfECMAScript?2019看Object.prototype.valueOfArray.prototype.valueOf:19.1.3.7Object.prototype.valueOf()调用valueOf方法时,采取以下步骤:返回?ToObject(这个值)。此函数是%ObjProto_valueOf%内部对象。让我们看看ToObject[疯狂,但坚持下去]。ToObjectECMAScript?2019:7.1.13ToObject(参数)对象:返回参数?!这一步是白来的。接下来我们看toString,同样要考虑覆盖的问题。Array.prototype.toString()ECMAScript?2019:22.1.3.28Array.prototype.toString()让数组成为?ToObject(thisvalue).让func为?Get(array,"join")。如果IsCallable(func)为false,则将func设置为内部函数%ObjProto_toString%.Return?调用(函数,数组)。可以看出join方法调用的是【ps:这里也有个小故事。有一次去滴滴面试,双方聊到这个问题,我说数组的toString调用了join,面试官告诉我,不要光看数组就揣测内部实现调用的结果,你不是这么想问题的……我摇了摇头,结果阻止了我,猎头反馈连续拒绝了三个:方向不符,不适合我们,滚蛋。???]我们非常努力地走到了这一步[].valueOf().toString()==0=>[].join()==0=>''==0如果你也认真看在这一点,不妨在博客上提个issue,留下联系方式,交个朋友^_^。然后我们看到双方还是不同的类型,所以还要继续进行类型转换,我们回到7.2.14抽象相等比较的步骤45,如果Type(x)是Number,Type(y)是String,返回比较结果x==!至编号(y)。如果Type(x)为String且Type(y)为Number,则返回比较结果!ToNumber(x)==y。可以看出''需要ToNumber,我们在上面描述的ToNumber和转换映射表中列出了,表中明确写明“String参见下面的语法和转换算法。”......ToNumber应用于StringTypeECMAScript?2019:7.1.3.1ToNumberAppliedtotheStringTypeUnfortunately,thisstepisdescribed非常抽象StringNumericLiteral:::StrWhiteSpaceoptStrWhiteSpaceoptStrNumericLiteralStrWhiteSpaceoptStrWhiteSpace:::StrWhiteSpaceCharStrWhiteSpaceoptStrWhiteSpaceChar:::WhiteSpaceLineTerminatorStrNumericLiteral:::StrDecimalLiteralBinaryIntegerLiteralOctalIntegerLiteralHexIntegerLiteralStrDecimalLiteral:::StrUnsignedDecimalLiteral+StrUnsignedDecimalLiteral-StrUnsignedDecimalLiteralStrUnsignedDecimalLiteral:::无穷DecimalDigits.DecimalDigitsoptExponentPartopt.DecimalDigitsExponentPartoptDecimalDigitsExponentPartoptDecimalDigitsExponentPartopt具体分解如下:ECMAScript?2019:11.8.3NumericLiterals摘自一些我们需要用到的东西:...DecimalIntegerLiteral::0NonZeroDigitDecimaleyes,isConfirmedsomeonecanoptIDecimaleyes,is想不通!整个过程大致描述了如果字符串中只包含数字(包括前面有加号或减号的情况),则将其转换为十进制值,即“1”变为1,“123”变为123,"011"将变为11(注意:忽略前导零);如果字符串包含有效的浮点格式,例如“1.1”,它将被转换为相应的浮点值(同样,前导零也被忽略);如果字符串包含有效的十六进制格式,例如“0xf”,则将其转换为相同大小的十进制整数值;八进制二进制也是如此;如果字符串为空(不包含任何字符),则将其转换为0;如果字符串包含上述格式以外的字符,则转换为NaN但我们还有Mozilla:Number("")//0所以最后的答案转换为:''==0=>0==0哦,大哥,原来这tm有惊喜啊!哥,我愿意……我愿意做鬼!最终答案真正的废话Q:这是不是矫枉过正了?A:当我在博客上提到这个问题时,我真的非常彻底地跟进了它。里面涉及到的规范都解释的很详细,其实面试的时候你只需要知道一些重点:左边的空数组没有转成bool而是转成number。将空数组转换为数字时如何调用valueOf和toString?调用顺序和调用规则是什么?“==”近似隐式转换规则。这些文字是否在jsfalse中转换为bool?但如果博客只写这四点,那你看了还是知其然不知其所以然。所以我写的比较详细。同时也是因为网上很多博客总是搞不清楚valueOf和toString的调用(顺序,为什么都调用),转几遍就开始乱写,==的转换规则都是模糊的不是借口,所以我只是想把这个问题说清楚。问:会有人认真读这个吗?答:是的。问:这样做有什么用?A:没用,下一个。【ps:面试的时候经常被问到这个问题。我觉得这本质上是你自己定位的问题。如果你把自己定位为前端,你就去学应用层。如果你把自己定位为程序员,你就会看全栈。如果你把自己定位为工程师,就看底层,看规格书。工作五年以上的程序员应该不会问这个问题。【pss:我把自己定位为兴趣爱好,所以就瞎看了】】Q:在工作中有用吗?工作这么忙哪有时间?A:通过。Q:厌倦了这样写博客?A:我很累。我要查很多资料,筛选。很多英文文件对于持有“大不列颠负十英文认证”的我来说就像是美国版的诗经。一个博客,至少三四天。而且大家好像都需要基础和成本,不知道能撑多久。问:...答:...如果您也有问题,请打开一个问题并添加。不想踩坑可以养,也可以养。blog:https://github.com/HCThink/h-blogissue:https://github.com/HCThink/h-blog/issues花瘦!ToNumber:!前缩ECMAScript?2019:5.2.3.4ReturnIfAbruptShorthands同理,前缀!用于指示以下对抽象或语法导向操作的调用将永远不会返回突然完成[术语“突然完成”指的是具有[[Type]]值而非正常值的任何完成。]并且由此产生的结果CompletionRecord的[[Value]]字段应该用来代替操作的返回值。例如,步骤:让val成为!OperationName().等效于以下步骤:令val为OperationName()。断言:val永远不会突然完成。如果val是完成记录,则将val设置为val.[[Value]]。运行时语义通过放置!或者?在调用操作之前:执行!SyntaxDirectedOperationofNonTerminal.大意是:!后面的语言操作的调用永远不会返回突然的完成,我理解是一定会执行一个预期结果类型,执行步骤就是上面的1、2、3步!ToNumber描述的是必须将操作数转换为数字类型并返回val.[[value]]?至编号:?ECMAScript?2019:5.2.3.4ReturnIfAbruptShorthandsInvocations以?为前缀的抽象操作和语法导向操作的调用指示ReturnIfAbrupt应应用于生成的完成记录。extend[]==![][]==[][]==false[]==0[]==''//【注意转换过程,不会转换成数字,看下篇问题][]=='0'{}=='0'
