原創禁止私自轉載前端
部門長期招收大量研發崗位【前端,後端,算法】,歡迎各位大神投遞,踊躍嘗試。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.