背景
为3月份跳槽做面试准备,无意刷到一题 实现一个add函数 ,不知道为什么要给函数重写 toString 方法,后经过各种查询发现 console.log 方法会自动调用toSting方法???
基本上,所有JS数据类型都拥有这两个方法,null 除外。它们俩是位于原型链上的方法,也是为了解决javascript 值运算 与 显示 的问题。
valueOf
和 toString
几乎都是在出现操作符 (+ - * / == > <)
时被调用(隐式转换)。
toString
方法返回一个表示该对象的字符串,当对象表示为文本值或以期望的字符串方式被引用时,toString 方法会被自动调用。
⬆️ MDN对Object.prototype.toString()的描述
1.手动调用看看是什么效果
let num = 123;
let str = '123'
let arr = [1, 2, 3];
let obj = {};
let fn = function () { console.log('我是fn函数') }
console.log(num.toString()) // 123
console.log(str.toString()) // 123
console.log(arr.toString()) // 1,2,3
console.log(obj.toString()) // [object Object]
console.log(fn.toString()) // function () { console.log('我是fn函数') }
嗯,和介绍的一样,没有骗人,全部都转成了字符串。
比较特殊的是,表示数组的时候,就变成了数组内容以逗号连接的字符串相当于 Array.join(',')
。
表示对象的时候,变成 [object Object] ,这是由于,Object.prototype.toString
返回 “[object type]”,其中 type
是对象的类型。
🌟 我们可以利用这个特点,借用 Object 原型上的 toString() 方法进行 类型判断 。
2.何时会自动调用呢
使用操作符的时候,如果其中一边为对象(复杂数据类型),则会先调用 toString
方法,也就是先 隐式转换 ,然后再进行运算符操作。
let arr = [1];
let obj = { a: 2 };
let fn = function () { console.log('我是fn函数') };
// 重写原型上的toString方法
Number.prototype.toString = function () {
console.log('重写的Number的toString方法');
}
String.prototype.toString = function () {
console.log('重写的String的toString方法');
}
Array.prototype.toString = function () {
console.log('重写的Array的toString方法');
return this.join(','); // 返回toString的默认值(下面测试)
}
Object.prototype.toString = function () {
console.log('重写的Object的toString方法');
}
Function.prototype.toString = function () {
console.log('重写的Function的toString方法');
return 'fn的返回值';
}
// 测试
console.log(2 + 1) // 3
console.log('str') // 'str'
console.log('str' + 2) // 'str2'
console.log('10' > 9) // true
console.log(arr < 2) // true (一次 => 重写的Array的toString方法)
console.log(arr + arr) // "11" (两次 => 重写的Array的toString方法)
console.log(obj > obj) // false (两次 => 重写的Object的toString方法)
console.log(fn + 1000) // fn的返回值1000 (一次 => 重写的Function的toString方法)
结论:只有复杂数据类型在需要做隐式转换时,会自动调用原型上的 toString 方法。
注意:此外,console.log(函数)也会自动调用 Function.prototype.toString()
3.重写 toString 方法
既然知道了有 toString 这个默认方法,那我们也可以来重写这个方法或则覆盖
class A {
constructor(count) {
this.count = count;
}
toString() {
return '我有这么多钱:' + this.count;
}
}
let a = new A(888);
console.log(a); // A {count: 888}
console.log(a.toString()); // 我有这么多钱:888
console.log(a + 1); // 我有这么多钱:8881
valueOf
返回当前对象的原始值
具体功能与 toString 大同小异,同样具有以上的自动调用和重写方法。
let num = 123;
let str = '123';
let arr = [1, 2, 3]
let obj = { name: 'syukinmei', age: 18 };
let fn = function () { console.log('我是fn函数') };
console.log(num.valueOf()); // 123
console.log(str.valueOf()); // 123
console.log(arr.valueOf()); // [1, 2, 3]
console.log(obj.valueOf()); // {name: 'syukinmei', age: 18}
console.log(fn.valueOf()); // ƒ () { console.log('我是fn函数') }
JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同。
两者区别
- 共同点:在输出对象时会自动调用。
- 不同点:默认返回不同,且存在优先级关系。
执行顺序
valueOf偏向于运算,toString偏向于显示。
所以:两者并存的情况下,首先调用 valueOf 如果valueOf 返回的值不能完成计算(也就是说valueOf返回的复杂数据类型时)还需要进行转换,再调用 toString 。
// 重写 Array 原型上的 toString
Array.prototype.toString = function () {
console.log('重写的Array的toString方法', this);
return this.join(','); // 返回toString的默认值(下面测试)
}
// Array 原型上没有valueOf 方法
// 重写 Object 原型上的 valueOf 方法
Object.prototype.valueOf = function () {
console.log('重写的Object的valueOf方法', this);
return this;
}
let arr = [1]
let arr2 = [2]
console.log(arr + arr2) // 12 arr先调用valueOf 再调用toString ,arr2再调用valueOf 再调用toString
如果我们改了valueOf 返回的是数字可以让直接计算 就不会再调用 toString 了。
// 重写 Array 原型上的 toString
Array.prototype.toString = function () {
console.log('重写的Array的toString方法', this);
return this.join(','); // 返回toString的默认值(下面测试)
}
// Array 原型上没有valueOf 方法
// 重写 Object 原型上的 valueOf 方法
Object.prototype.valueOf = function () {
console.log('重写的Object的valueOf方法', this);
return 11; // 〈=== 直接返回数字,其他基本数据类型都可以
}
let arr = [1]
let arr2 = [2]
console.log(arr + arr2) // 2 arr先调用valueOf,arr2再调用valueOf
面试题分析
以下几道大厂必考的面试题,完美呈现出 toString 与 value 的作用。
a==1&&a==2&&a==3 为 true
双等号(==):会触发 隐式转化,即会调用 valueOf
或者 toString
。
每次判断都会触发 valueOf
方法,让 value+1 使得下次判断成立。
class A {
constructor(value) {
this.value = value;
}
valueOf() {
console.log('valueOf')
return this.value++;
}
}
const a = new A(1);
if (a == 1 && a == 2 && a == 3) {
console.log('Amazed hello world');
}
console.log(a);
a === 1 && a === 2 && a === 3 为 true
全等运算符(===)不会触发 隐式转换 ,这里我们就可以使用 Object.defineProerty
数据劫持的方法来实现。
let initialValue = 1;
Object.defineProperty(window, 'a', {
get() {
console.log('a被访问');
return initialValue++;
},
})
if (a === 1 && a === 2 && a === 3) {
console.log('Amazed hello world');
}
// a被访问
// a被访问
// a被访问
// Amazed hello world
上面我们就是劫持全局 window 上的 a ,当 a 每一次做判断的时候都会触发 get 方法获取值,我们在每一次执行 get 方法时实现一次 initialValue 自增,判断三次就自增三次,所以最后会让公式成立。