从 JS 中的 valueOf 谈开
ValueOf 是 JavaScript 中 Object 原型上少数几个方法之一,应该不能算是很偏门的函数,但是只有《JavaScript权威指南》上面有只言片语的描述,而对 ES2015 中相应的 Symbol.toPrimitive
更是鲜有提及。
首先,给出一个结论,valueOf
和 toString
几乎都是在出现操作符(+-*/==><)时被调用, 并且 valueOf
几乎没有什么用。
那么,valueOf
的作用是什么呢?按照语言标准上的说法就是,用于 toPrimitive
需要 Number 时,而 toPrimitive
出现的时机,说得简单一点就是,当需要一个 Number 或者 String,但是被传入了一个对象时,就会执行这个操作。有一个常见的用法实际上是使用了 valueOf
:
将 Date 对象转换为时间戳时,会很自然的用 + new Date()
, 实际上是悄悄地调用了 new Date().valueOf()
详细地说,有如下场景,会出现偏好结果是 Number 的 toPrimitive
,也就是说 valueOf
可能被调用(如果没有 valueOf
的话,可以被 toString
等替代):
ToNumber
具体而言,当 obj 前后操作符是加法,以及减法、乘法、除法,以及调用 Number(obj)
以及 new Number(obj)
时。值得注意的是,parseInt
,parseFloat
等方法实际上不会调用 ToNumber
,他们调用的是偏好 String 的 toPrimitive
。举个代码的例子:
class Test { valueOf() { return 1; } toString() { return '2'; } } const a = new Test(); console.log(parseInt(a, 10)); // 打印出2,也就是toString被调用了 console.log(Number(a));// 打印出1,也就是valueOf被调用了 const obj = {}; obj[a] = 1; console.log(obj); // 打印出 {‘2’: 1},也就是toString被调用了 const b = 0; const c = '0'; console.log(a + b); // 打印出 1, 也就是valueOf被调用了 console.log(a + c);// 打印出 10, 也就是还是ValueOf被调用
比较大小
直接上代码:
class Test { constructor(val) { this.__val = val; } valueOf() { return this.__val; } } const a = new Test('12'); const b = new Test(2); console.log(a < b); // 打印出false,说明valueOf被调用,且两者不都是string时,转换为Number // 且如果不能转为Number,则值为NaN
我们可以得出如下结论,在应该使用 值,也就是数值的地方,如果出现了对象,就会调用 valueOf。再给出一个例子,证明 valueOf
不存在时,可以用 toString
作为替代品
class Test { toString() { return '2'; } } const a = new Test(); console.log(parseInt(a, 10)); // 打印出2,也就是toString被调用了 console.log(Number(a));// 打印出2,也就是toString被调用了 const obj = {}; obj[a] = 1; console.log(obj); // 打印出 {‘2’: 1},也就是toString被调用了 const b = 0; const c = '0'; console.log(a + b); // 打印出 1, 也就是toString被调用了 console.log(a + c);// 打印出 10, 也就是还是toString被调用
但是反过来是不成立的,有的地方必须使用 toString
,比如说把对象作为对象的key使用时,以及向 parseInt
中传入对象时。
到这里,可以得出一个结论,一个对象想要在希望转化为数字的地方,通过给出特殊的 valueOf
来给出不同于期望转化为字符串的地方的值,最好的例子就是 Date
对象。
另外,我们频繁提到的 toPrimitive
这个操作,在 ES2015 标准中,已经真的添加了这个方法,并且这个方法会比 toString
和 valueOf
的优先级都高,并且嘛,几乎都可以替代这俩货了, 给个例子:
class Test { valueOf() { return 1; } toString() { return '2'; } [Symbol.toPrimitive](hint) { console.log(hint); return 3; } } let a = new Test(); const obj = {}; obj[a] = 1; console.log(obj); // => string { '3': 1 } const b = 0; const c= '0'; console.log(a + b); // => default 3, default相当于number console.log(a + c); // => default 30 console.log(parseInt(a, 10)); // => string 3 console.log(Number(a)); // => number 3
上述代码中 Symbol.toPrimitive
方法可以接收参数,表示期望的类型,就像我们提到的,如果是string,就是期望获得字符串(也就是之前说的调用 toString),如果是 default 或者 number 则希望获取一个数字(也就是之前说的优先调用 valueOf,否则调用 toString)。
可以看出,Symbol.toPrimitive
是完完全全可以取代掉 valueOf
,甚至 toString
。
另外,++ 运算符也可以触发偏好 Number 的 toPrimitive
,而且很有意思的是 toPrimitive
系是内建函数,可以返回左值,所以可以用到对象上:
class Test { constructor(val) { this.__val = val; } [Symbol.toPrimitive](hint) { return this.__val; } } let a = new Test(1); console.log(++a); // => 2 let b = new Test('2'); b++; console.log(b); // => 3 let c = '1'; console.log(typeof c) // => 'string' console.log(++c) console.log(typeof c) // => 'numer', ++ 确实有转型的作用
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论