据悉,这道题好像是京东考的。
题目
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]] 内部方法并输出结果。
结论
本题考查的是类型转换后获取原始值进行对比。