从JS中的valueOf谈开
Opened this issue · 0 comments
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', ++ 确实有转型的作用