背景

为3月份跳槽做面试准备,无意刷到一题 实现一个add函数 ,不知道为什么要给函数重写 toString 方法,后经过各种查询发现 console.log 方法会自动调用toSting方法???

基本上,所有JS数据类型都拥有这两个方法,null 除外。它们俩是位于原型链上的方法,也是为了解决javascript 值运算 与 显示 的问题。

valueOftoString 几乎都是在出现操作符 (+ - * / == > <) 时被调用(隐式转换)。

image.png

toString

方法返回一个表示该对象的字符串,当对象表示为文本值或以期望的字符串方式被引用时,toString 方法会被自动调用。

image.png
⬆️ MDN对Object.prototype.toString()的描述

1.手动调用看看是什么效果

  1. let num = 123;
  2. let str = '123'
  3. let arr = [1, 2, 3];
  4. let obj = {};
  5. let fn = function () { console.log('我是fn函数') }
  6. console.log(num.toString()) // 123
  7. console.log(str.toString()) // 123
  8. console.log(arr.toString()) // 1,2,3
  9. console.log(obj.toString()) // [object Object]
  10. console.log(fn.toString()) // function () { console.log('我是fn函数') }

嗯,和介绍的一样,没有骗人,全部都转成了字符串。

比较特殊的是,表示数组的时候,就变成了数组内容以逗号连接的字符串相当于 Array.join(',')
表示对象的时候,变成 [object Object] ,这是由于,Object.prototype.toString 返回 “[object type]”,其中 type 是对象的类型。
🌟 我们可以利用这个特点,借用 Object 原型上的 toString() 方法进行 类型判断 。
image.png

2.何时会自动调用呢

使用操作符的时候,如果其中一边为对象(复杂数据类型),则会先调用 toString 方法,也就是先 隐式转换 ,然后再进行运算符操作。

  1. let arr = [1];
  2. let obj = { a: 2 };
  3. let fn = function () { console.log('我是fn函数') };
  4. // 重写原型上的toString方法
  5. Number.prototype.toString = function () {
  6. console.log('重写的Number的toString方法');
  7. }
  8. String.prototype.toString = function () {
  9. console.log('重写的String的toString方法');
  10. }
  11. Array.prototype.toString = function () {
  12. console.log('重写的Array的toString方法');
  13. return this.join(','); // 返回toString的默认值(下面测试)
  14. }
  15. Object.prototype.toString = function () {
  16. console.log('重写的Object的toString方法');
  17. }
  18. Function.prototype.toString = function () {
  19. console.log('重写的Function的toString方法');
  20. return 'fn的返回值';
  21. }
  22. // 测试
  23. console.log(2 + 1) // 3
  24. console.log('str') // 'str'
  25. console.log('str' + 2) // 'str2'
  26. console.log('10' > 9) // true
  27. console.log(arr < 2) // true (一次 => 重写的Array的toString方法)
  28. console.log(arr + arr) // "11" (两次 => 重写的Array的toString方法)
  29. console.log(obj > obj) // false (两次 => 重写的Object的toString方法)
  30. console.log(fn + 1000) // fn的返回值1000 (一次 => 重写的Function的toString方法)

image.png
结论:只有复杂数据类型在需要做隐式转换时,会自动调用原型上的 toString 方法。
注意:此外,console.log(函数)也会自动调用 Function.prototype.toString()

3.重写 toString 方法

既然知道了有 toString 这个默认方法,那我们也可以来重写这个方法或则覆盖

  1. class A {
  2. constructor(count) {
  3. this.count = count;
  4. }
  5. toString() {
  6. return '我有这么多钱:' + this.count;
  7. }
  8. }
  9. let a = new A(888);
  10. console.log(a); // A {count: 888}
  11. console.log(a.toString()); // 我有这么多钱:888
  12. console.log(a + 1); // 我有这么多钱:8881

valueOf

返回当前对象的原始值

具体功能与 toString 大同小异,同样具有以上的自动调用和重写方法。

  1. let num = 123;
  2. let str = '123';
  3. let arr = [1, 2, 3]
  4. let obj = { name: 'syukinmei', age: 18 };
  5. let fn = function () { console.log('我是fn函数') };
  6. console.log(num.valueOf()); // 123
  7. console.log(str.valueOf()); // 123
  8. console.log(arr.valueOf()); // [1, 2, 3]
  9. console.log(obj.valueOf()); // {name: 'syukinmei', age: 18}
  10. console.log(fn.valueOf()); // ƒ () { console.log('我是fn函数') }

image.png

JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同。
image.png

两者区别

  • 共同点:在输出对象时会自动调用。
  • 不同点:默认返回不同,且存在优先级关系。

执行顺序

valueOf偏向于运算,toString偏向于显示。
所以:两者并存的情况下,首先调用 valueOf 如果valueOf 返回的值不能完成计算(也就是说valueOf返回的复杂数据类型时)还需要进行转换,再调用 toString 。

  1. // 重写 Array 原型上的 toString
  2. Array.prototype.toString = function () {
  3. console.log('重写的Array的toString方法', this);
  4. return this.join(','); // 返回toString的默认值(下面测试)
  5. }
  6. // Array 原型上没有valueOf 方法
  7. // 重写 Object 原型上的 valueOf 方法
  8. Object.prototype.valueOf = function () {
  9. console.log('重写的Object的valueOf方法', this);
  10. return this;
  11. }
  12. let arr = [1]
  13. let arr2 = [2]
  14. console.log(arr + arr2) // 12 arr先调用valueOf 再调用toString ,arr2再调用valueOf 再调用toString

image.png

如果我们改了valueOf 返回的是数字可以让直接计算 就不会再调用 toString 了。

  1. // 重写 Array 原型上的 toString
  2. Array.prototype.toString = function () {
  3. console.log('重写的Array的toString方法', this);
  4. return this.join(','); // 返回toString的默认值(下面测试)
  5. }
  6. // Array 原型上没有valueOf 方法
  7. // 重写 Object 原型上的 valueOf 方法
  8. Object.prototype.valueOf = function () {
  9. console.log('重写的Object的valueOf方法', this);
  10. return 11; // 〈=== 直接返回数字,其他基本数据类型都可以
  11. }
  12. let arr = [1]
  13. let arr2 = [2]
  14. console.log(arr + arr2) // 2 arr先调用valueOf,arr2再调用valueOf

image.png

面试题分析

以下几道大厂必考的面试题,完美呈现出 toString 与 value 的作用。

a==1&&a==2&&a==3 为 true

双等号(==):会触发 隐式转化,即会调用 valueOf 或者 toString

每次判断都会触发 valueOf 方法,让 value+1 使得下次判断成立。

  1. class A {
  2. constructor(value) {
  3. this.value = value;
  4. }
  5. valueOf() {
  6. console.log('valueOf')
  7. return this.value++;
  8. }
  9. }
  10. const a = new A(1);
  11. if (a == 1 && a == 2 && a == 3) {
  12. console.log('Amazed hello world');
  13. }
  14. console.log(a);

image.png

a === 1 && a === 2 && a === 3 为 true

全等运算符(===)不会触发 隐式转换 ,这里我们就可以使用 Object.defineProerty 数据劫持的方法来实现。

  1. let initialValue = 1;
  2. Object.defineProperty(window, 'a', {
  3. get() {
  4. console.log('a被访问');
  5. return initialValue++;
  6. },
  7. })
  8. if (a === 1 && a === 2 && a === 3) {
  9. console.log('Amazed hello world');
  10. }
  11. // a被访问
  12. // a被访问
  13. // a被访问
  14. // Amazed hello world

上面我们就是劫持全局 window 上的 a ,当 a 每一次做判断的时候都会触发 get 方法获取值,我们在每一次执行 get 方法时实现一次 initialValue 自增,判断三次就自增三次,所以最后会让公式成立。

实现一个无限累加函数