一道有趣的面试题

据悉,这道题好像是京东考的。

题目

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

解答

object方式

1
2
3
4
5
6
var x = {
a: 1,
valueOf: function () {
return this.a++;
}
}
1
2
3
4
5
6
var x = {
a: 1,
toString: function () {
return this.a++;
}
}

array方式

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

function方式

1
2
3
4
5
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]] 内部方法并输出结果。

结论

本题考查的是类型转换后获取原始值进行对比。

“我,鸡哥,打钱!”