一道有趣的面試題

據悉,這道題好像是京東考的。

題目

var x = ?
// 如何令 x == 1 && x == 2 && x == 3

解答

object方式

var x = {
    a: 1,
    valueOf: function () {
        return this.a++;
    }
}
var x = {
    a: 1,
    toString: function () {
        return this.a++;
    }
}

array方式

var x = [1, 2, 3];
x.valueOf = function () {
    return this.shift();
}
var x = [1, 2, 3];
x.toString = function () {
    return this.shift();
}
var x = [1,2,3];
x.join = x.shift;

function方式

var x = function () {};
x.a = 1;
x.toString = function () {
    return this.a
}

本質

請看ecmascript 262規格書

抽象相等比較算法

比較運算 x==y, 其中 x 和 y 是值,產生 true 或者 false。這樣的比較按如下方式 進行:

  • 若Type(x)與Type(y)相同,則

    • 若Type(x)爲Undefined,返回true。
    • 若Type(x)爲Null,返回true。
    • 若Type(x)爲Number,則

      • 若x爲NaN,返回false。
      • 若y爲NaN,返回false。
      • 若x與y爲相等數值,返回true。
      • 若x 爲 +0 且 y爲−0, 返回true。
      • 若x 爲 −0 且 y爲+0, 返回true。
      • 返回 false。
    • 若 Type(x)爲 String, 則當 x 和 y 爲完全相同的字符序列(長度相等且相同 字符在相同位置)時返回 true。 否則, 返回 false。
    • 若Type(x)爲Boolean, 當x和y爲同爲true或者同爲false時返回true。否 則, 返回 false。
    • 當x和y爲引用同一對象時返回true。否則,返回false。
  • 若x爲null且y爲undefined, 返回true。
  • 若x爲undefined且y爲null, 返回true。
  • 若 Type(x) 爲 Number 且 Type(y)爲 String, 返回 comparison x == ToNumber(y)的結果。
  • 若 Type(x) 爲 String 且 Type(y)爲 Number,

    • 返回比較 ToNumber(x) == y 的結果。
  • 若 Type(x)爲 Boolean, 返回比較 ToNumber(x) == y 的結果。
  • 若 Type(y)爲 Boolean, 返回比較 x == ToNumber(y)的結果。
  • 若Type(x)爲String或Number,且Type(y)爲Object,返回比較x== ToPrimitive(y)的結果。
  • 若 Type(x)爲 Object 且 Type(y)爲 String 或 Number, 返回比較 ToPrimitive(x) == y 的結果。
  • 返回 false。

本質上,本題利用以下兩條抽象比較規則

  • 若 Type(x) 爲 String 且 Type(y)爲 Number,

    • 返回比較 ToNumber(x) == y 的結果。
  • 若 Type(x)爲 Object 且 Type(y)爲 String 或 Number, 返回比較 ToPrimitive(x) == y 的結果。

實際上,三者都是Object,不明確此處爲何需要自行翻閱ecma262的規格書以及複習基本類型

那麼,爲什麼用valueOf,toString, 覆蓋join都能實現呢,請查看以下規格書定義:

ToPrimitive

ToPrimitive 運算符接受一個值,和一個可選的 期望類型 作參數。ToPrimitive 運算符把其值參數轉換爲非對象類型。如果對象有能力被轉換爲不止一種原語類 型,可以使用可選的 期望類型 來暗示那個類型。根據下表完成轉換:

此處我們只關注Object:

  • Object

    • 返回該對象的默認值。(調用該對象的內部方法[[DefaultValue]]一樣)

關注[[DefaultValue]](hint)

對象內部方法[[DefaultValue]](hint)

當用字符串 hint 調用 O 的 [[DefaultValue]] 內部方法,採用以下步驟:

  • 令 toString 爲用參數 "toString" 調用對象 O 的 [[Get]] 內部方法的結果。
  • 如果 IsCallable(toString) 是 true,則

    • 令 str 爲用 O 作爲 this 值,空參數列表調用 toString 的 [[Call]] 內部方法的結果.
    • 如果 str 是原始值,返回 str。
    • 令 valueOf 爲用參數 "valueOf" 調用對象 O 的 [[Get]] 內部方法的結果。
    • 如果 IsCallable(valueOf) 是 true,則

      • 令 val 爲用 O 作爲 this 值,空參數列表調用 valueOf 的 [[Call]] 內部方法的結果。
      • 如果 val 是原始值,返回 val。
    • 拋出一個 TypeError 異常。

當用數字 hint 調用 O 的 [[DefaultValue]] 內部方法,採用以下步驟:

  • 令 valueOf 爲用參數 "valueOf" 調用對象 O 的 [[Get]] 內部方法的結果。
  • 如果 IsCallable(valueOf) 是 true,則

    • 令 val 爲用 O 作爲 this 值,空參數列表調用 valueOf 的 [[Call]] 內部方法的結果。
    • 如果 val 是原始值,返回 val。
    • 令 toString 爲用參數 "toString" 調用對象 O 的 [[Get]] 內部方法的結果。
    • 如果 IsCallable(toString) 是 true,則

      • 令 str 爲用 O 作爲 this 值,空參數列表調用 toString 的 [[Call]] 內部方法的結果。
      • 如果 str 是原始值,返回 str。
    • 拋出一個 TypeError 異常。

當不用 hint 調用 O 的 [[DefaultValue]] 內部方法,O 是 Date 對象的情況下 彷彿 hint 是字符串一樣解釋它的行爲,除此之外彷彿 hint 是數字一樣解釋它 的行爲。

上面說明的 [[DefaultValue]] 在原生對象中只能返回原始值。如果一個宿主對象 實現了它自身的 [[DefaultValue]] 內部方法,那麼必須確保其 [[DefaultValue]] 內部方法只能返回原始值。

既然已經知道了規則,那麼我們需要關注以下兩個方法。

關注valueOf及toString方法

valueOf MDN描述

toString MDN描述

爲何複寫join一樣有效

規格書中規定當調用toString方法時, 以 "join" 作爲參數調用 array 的 [[Get]] 內部方法並輸出結果。

結論

本題考查的是類型轉換後獲取原始值進行對比。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章