从 JS 中的 valueOf 谈开

发布于 2022-06-13 12:40:48 字数 4600 浏览 1062 评论 0

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) 时。值得注意的是,parseIntparseFloat 等方法实际上不会调用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
23 人气
更多

推荐作者

已经忘了多久

文章 0 评论 0

15867725375

文章 0 评论 0

LonelySnow

文章 0 评论 0

走过海棠暮

文章 0 评论 0

轻许诺言

文章 0 评论 0

信馬由缰

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文