
1.5 toString()函数与valueOf()函数
在1.4.1小节关于等于运算符的内容中,如果比较的内容包含对象类型数据,则会涉及隐式转换,那么就会调用toString()函数和valueOf()函数。本节会详细讲解toString()函数与valueOf()函数,并通过实例来看看它们的使用场景。
在JavaScript中,toString()函数与valueOf()函数解决的是值的显示和运算的问题,所有引用类型都拥有这两个函数。
1. toString()函数
toString()函数的作用是把一个逻辑值转换为字符串,并返回结果。Object类型数据的toString()函数默认的返回结果是"[object Object]",当我们自定义新的类时,可以重写toString()函数,返回可读性更高的结果。
在JavaScript中,Array,Function,Date等类型都实现了自定义的toString()函数。
· Array的toString()函数返回值为以逗号分隔构成的数组成员字符串,例如[1, 2,3].toString()结果为字符串'1,2,3'。
· Function的toString()函数返回值为函数的文本定义,例如(function(x){return x *2;}).toString()的结果为字符串"function(x){return x * 2;}"。
· Date的toString()函数返回值为具有可读性的时间字符串,例如,new Date().toString()的结果为字符串"Sun Nov 25 2018 15:00:16 GMT+0800 (中国标准时间)"。
2. valueOf()函数
valueOf()函数的作用是返回最适合引用类型的原始值,如果没有原始值,则会返回引用类型自身。Object类型数据的valueOf()函数默认的返回结果是"{}",即一个空的对象字面量。
对于Array、Function、Date等类型,valueOf()函数的返回值是什么呢?
· Array的valueOf()函数返回的是数组本身,例如[1, 2, 3].valueOf()返回的结果为“[1,2,3]”。
· function的valueOf()函数返回的是函数本身,例如(function(x){return x * 2;}).valueOf()返回的结果为函数本身“function(x){return x * 2;}”。
· Date的valueOf()函数返回的是指定日期的时间戳,例如new Date().valueOf()返回的结果为“1543130166771”。
如果一个引用类型的值既存在toString()函数又存在valueOf()函数,那么在做隐式转换时,会调用哪个函数呢?
这里我们可以概括成两种场景,分别是引用类型转换为String类型,以及引用类型转换为Number类型。
1. 引用类型转换为String类型
一个引用类型的数据在转换为String类型时,一般是用于数据展示,转换时遵循以下规则。
· 如果对象具有toString()函数,则会优先调用toString()函数。如果它返回的是一个原始值,则会直接将这个原始值转换为字符串表示,并返回该字符串。
· 如果对象没有toString()函数,或者toString()函数返回的不是一个原始值,则会再去调用valueOf()函数,如果valueOf()函数返回的结果是一个原始值,则会将这个结果转换为字符串表示,并返回该字符串。
· 如果通过toString()函数或者valueOf()函数都无法获得一个原始值,则会直接抛出类型转换异常。
我们通过以下代码进行测试。
var arr = []; arr.toString = function () { console.log('执行了toString()函数'); return []; }; arr.valueOf = function () { console.log('执行了valueOf()函数'); return []; }; console.log(String(arr));
上面代码执行后的结果如下所示。
执行了toString()函数 执行了valueOf()函数 TypeError: Cannot convert Object to primitive value
执行String(arr)代码时,需要将arr转换为字符串,则会优先执行toString()函数,但是其返回值为空数组[],并不能转换为原生数据;然后调用valueOf()函数,其返回值同样为空数组[];那么在调用完toString()函数和valueOf()函数后,均无法获取到原生数据类型表示,则抛出异常TypeError,表示无法将对象类型转换为原生数据类型。
2. 引用类型转换为Number类型
一个引用类型的数据在转换为Number类型时,一般是用于数据运算,转换时遵循以下规则。
· 如果对象具有valueOf()函数,则会优先调用valueOf()函数,如果valueOf()函数返回一个原始值,则会直接将这个原始值转换为数字表示,并返回该数字。
· 如果对象没有valueOf()函数,或者valueOf()函数返回的不是原生数据类型,则会再去调用toString()函数,如果toString()函数返回的结果是一个原始值,则会将这个结果转换为数字表示,并返回该数字。
· 如果通过toString()函数或者valueOf()函数都无法获得一个原始值,则会直接抛出类型转换异常。
我们通过以下代码进行测试。
var arr = []; arr.toString = function () { console.log('执行了toString()函数'); return []; }; arr.valueOf = function () { console.log('执行了valueOf()函数'); return []; }; console.log(Number(arr));
上面代码执行后的结果如下所示。
执行了valueOf()函数 执行了toString()函数 TypeError: Cannot convert Object to primitive value
执行Number(arr)代码时,需要将arr转换为数字,则会优先执行valueOf()函数,但是其返回值为空数组[],并不能转换为原生数据;然后调用toString()函数,其返回值同样为空数组[];那么在调用完valueOf()函数和toString()函数后,均无法获取到原生数据表示,则抛出异常TypeError,表示无法将对象类型转换为原生数据类型。
事实上,对除了Date类型以外的引用类型数据转换为原生数据类型时,如果是用于数据运算,则会优先调用valueOf()函数,在valueOf()函数无法满足条件时,则会继续调用toString()函数,如果toString()函数也无法满足条件,则会抛出类型转换异常。
如果是用于数据展示,则会优先调用toString()函数,在toString()函数无法满足条件时,则会继续调用valueOf()函数,如果valueOf()函数也无法满足条件,则会抛出类型转换异常。
了解了valueOf()函数和toString()函数的关系后,我们再用下面两组代码深入拓展一下其他相关知识。
拓展1
看看下面3行代码,它们的结果有什么不同。
[] == 0; // true [1] == 1; // true [2] == 2; // true
在第一行中,空数组可以转换为数字0;在第二行和第三行中,只有一个数字元素的数组可以转换为该数字。这是为什么呢?
因为数组继承了Object类型默认的valueOf()函数,这个函数返回的是数组自身,而不是原生数据类型,所以会继续调用toString()函数。数组调用toString()函数时会返回数组元素以逗号作为分隔符构成的字符串,那么空数组就转换为空字符串,而空字符串与数字0在非严格相等的情况下是相等的,即'' == 0,返回“true”。
同样,只包含一个数字的数组[1],转换后为字符串"1",后判断"1" == 1,返回“true”。
拓展2
以下是另外一组Object类型的数据,请观察结果有什么不同。
var obj = { i: 10, toString: function () { console.log('toString'); return this.i; }, valueOf: function () { console.log('valueOf'); return this.i; } }; +obj; // valueOf '' + obj; // valueOf String(obj); // toString Number(obj); // valueOf obj == '10'; // valueOf,true obj === '10'; // false
第一行执行代码为+obj,将对象obj转换为原始值,用于数据运算,优先调用valueOf()函数,获得原始值,结果为数字“10”。
第二行执行代码为'' + obj,将对象obj转换为原始值,用于数据运算,优先调用valueOf()函数,获取原始值,并与字符串进行拼接,结果为字符串"10"。
第三行执行代码为String(obj),在String()函数中,用于数据展示,优先调用toString()函数获取对象的字符串表示,结果为字符串"10"。
第四行执行代码为Number(obj),将对象obj转换为数值表示,用于数据运算,优先调用valueOf()函数,结果为数字“10”。
第五行执行代码为obj == '10',将对象obj转换为原始值,用于数据运算,优先调用valueOf()函数,即将10与'10'进行比较,两者是相等的,结果为“true”;
第六行执行代码为obj === '10',因为两者数据类型不一致,直接返回“false”,并不会执行toString()函数或者valueOf()函数。