你真的理解JavaScript中的toString和valueOf方法了吗?

发表时间: 2023-10-16 08:02

作者:极客小俊
把逻辑思维转变为代码的技术博主





摘要 我觉得你大概率还是没搞明白toString和valueOf这两个方法到底有什么用! 很多人都没有说清楚! 我看不见得吧!

前言

很多人见过toStringvalueOf这两个方法,但就是不清楚这两个方法用来干嘛的,如何运行的,今天就给大家详细介绍一下,但是在说之前,我们必须要对数据类型转换、面向对象、原型和原型链等知识点先需要回顾复习一下, 这样有助于更好的理解这两个方法!

数据类型转换

首先稍稍有点JS基础的朋友肯定是知道,在JS中数据类型转换是分为强制转换隐式转换 两种方式!

强制类型转换 也称为显性类型转换,意思就是你能看到的一种数据类型转换的情况

隐式类型转换 也称为自动类型转换,意思就是你可能无法感知的情况下数据类型就被转换了

强制数据类型转换

先来看看什么是强制类型转换

我们需要将值显式地转换为我们期望的数据类型!

这里我可以调用数据类型当中的toString()方法来完成对其他数据类型转换为String类型的操作

也就说对要被转换的数据类型调用toString()方法即可!

举个栗子

//定义一个数值类型的变量var num=100;//转换前console.log('转换[前]为:'+typeof num+'类型');//开始转换num=num.toString();//转换后console.log('转换[后]为:'+typeof num+'类型');

如图

这个案例很简单,快速的把一个数值类型强制转换为字符串类型 这没什么说的!

隐式数据类型转换

隐式数据类型转换又称为: 自动类型转换就是不需要人为强制进行数据的转换,而是JS会自动的将某种类型转换为需要的类型,所以该转换操作用户是感觉不到的,因此又称为 隐式类型转换

但其实内部还是和强制类型转换一样,也是通过隐性的调用String()、Number()、Boolean()函数来进行转换 ,不同的则是这种操作是由JS自己自动完成的!

所以从转换规则上说 隐式数据类型转换强制数据类型转换是一样的!

举个梨子

很多人不知道,其实alert方法会自动将任何要进行弹出打印的数据,都转换为字符串以进行显示, 很多人并不知道这一特点!

怎么你还不信?那么我们来测试测试看看!

举个栗子

你用alert打印数值、布尔值、还是字符串可能看不怎么出来,我们来打印一个函数试试看就知道了!

function test(){    return 1+1;}alert(test);

你看看打印结果是什么就明白了!

这里居然把函数体打印出来了,并且是以字符串的形式, 并且就测试以下代码看看,效果是不是一样的

function test(){    return 1+1;}console.log(test.toString());  //强制转换为字符串console.log(String(test));     //强制转换为字符串console.log(test+"");          //快捷隐式转换为字符串

如图

看到了吧,这很好的证明了隐式类型转换,其实和强制类型转换的规则上其实是差不多的!

并且JS在算术运算表达式中,也会自动将运算数据进行number类型转换

举个简单的栗子:

alert("6"/ "2"); 输出结果为3

这里就是左右两边String类型的数据被自动转换number类型,然后再进行算术运算得出结果!

JS面向对象

关于面向对象这一块我以前也有说过,这里也简单提一嘴,

对于JS面向对象而言,你可以把任何一样东西都看成对象,然后找出对象属性方法,通过这种对象方式的模块化来管理整个对象当中的数据,这就是面向对象编程 这个道理如果你学过C++、Java、PHP等语言其实大致上都是触类旁通的!

所以面向对象也是一种模块化编程的一种方式,而在我们给别人使用这个对象的时候,只需要让别人实例化这个对象,然后就可以访问对象中的属性和对象中的方法了

我们在平常的项目开发中使用最频繁的也是自定义的对象!

例如

var obj={    _getUser:function (){        console.log('获取用户信息逻辑');    },    _ViewOrder:function () {        console.log('查看订单');    }}console.log(obj);

初探toString与valueOf方法

上面说了那么多,又跟toString与valueOf这两个方法有什么关系呢?

我们来看个梨子:

var obj={"user":"张三"};console.log(String(obj));console.log(obj+"");

以上会返回一个为[object Object]的结果!

如图

尤其刚刚接触js的朋友这时候一定会想,咦.............这是个什么玩意东西呢?? 嘿嘿嘿

toString()方法

首先上面的代码中,我进行了把一个对象强制和隐式转换为字符串的行为,对吧!

可是呢,从JS设计的角度是不允许这样给对象进行处理的!

在此类运算的情况下,对象会被自动转换为原始值,然后对这些原始值进行运算,并得到运算结果(也是一个原始值)

于是呢对象字符串的转换,当我们对期望一个字符串的对象执行操作时,这时候就非常关键啦,嘿嘿

js将尝试寻找toStringvalueOf 方法,你也先别管这两个方法从哪里来的,我们先来看一下它的运行原理!

分析

  1. 当一个对象被转换到字符串时候,首先隐式调用了toString()方法,如果尝试能够返回出基本数据类型也就是(字符串、数值、布尔值)等等,则可以调用String()函数继续转换该值,最终返回出字符串
  2. 如果toString()方法 返回出来的不是基本数据类型, 也就是说如果它返回出来的依然是一个对象,那么则再继续调用valueOf()方法 如果返回出来的是基本数据类型的值,然后则可以继续用String()函数转换该值

怎么样,是不是听着有些神叨叨、鬼道道、神批唠叨的感觉还没明白? 没关系 我们看下面的案例来理解!

其实呀很简单,你先记住, 对于字符串转换,会默认调用toString方法

并且默认情况下toString 方法会返回一个字符串[object Object]

刚刚上面我们不是看到了对象转字符串会返回出一个[object Object]的东西嘛,其实我们自己也能去定义它

例如

var obj={    "user":"张三",    "toString":function () {        console.log('1.执行了toString()方法');        return '[object Object]';    }}console.log(String(obj));  //强制把对象转换为字符串console.log(""+obj);       //隐式把对象转换为字符串

如图

这就很明显确定了一件重要的事情,我们在给对象做字符串转换的时候,会默认调用这个toString方法

只是说这里我们重写了它一下, 并且如果尝试能够返回出基本数据类型也就是(字符串、数值、布尔值)等等,则可以继续调用String()函数继续转换该值,最终返回出我们想要的结果对吧!

那么看下面的代码: 我就给它返回一个其他的基本数据类型看看,

var obj={    "user":"张三",    "toString":function () {        console.log('1.执行了toString()方法');        return 100;  //从toString()方法中返回一个基本数据类型出去!    }}console.log(String(obj));console.log(""+obj);

如图

这里对象被进行转换了两次,一次显示,一次隐式,但不管是显示还是隐式的转换,其实都一样

所以说,作为toString()方法就是在进行对象被进行字符串转换的时候,被自动调用,然后返回一个基本数据类型出来! 并且toString()方法必须要返回基本数据类型,才能够被String()函数继续进行字符串的转换, 至于要返回什么基本数据出来,这里由我们自己决定!

valueOf()方法

valueOf其实默认情况下返回的就是对象自身

那么问题来了,如果说toString()方法 返回出来的不是基本数据类型, 也就是说如果它返回出来的是一个对象, 会怎么样呢?

现在我告诉你,则会继续调用valueOf()方法 如果返回基本数据类型的值,

按照我们上面代码的意思,则又可以继续用String()函数转换该值!

所以此时此刻,你可以把这个valueOf()方法加上去看看了!

代码如下

var obj={    "user":"张三",    "toString":function () {        console.log('1.执行了toString()方法');        return {};    },    "valueOf":function (){        console.log('2.执行了valueOf()方法');        return '执行结束';    }}console.log(String(obj));

代码分析

此时,我为了让valueOf方法执行,在toString方法中故意返回一个对象, 那么valueOf方法自然会自动执行,最后返回出一个基本数据类型(标量类型)出来!

并且在这里我们也很好的知道了toString和valueOf两个方法执行的先后顺序!

总之你先记住: 对于字符串转换,优先调用 toString方法、然后再调用valueOf方法

如图

toString与valueOf方法的顺序问题

对于字符串转换,顺序很简单,也就是先执行toString再执行valueOf 这个上面已经说过了!

但是如果只是对于数学运算,那么则优先调用 valueOf 方法, 除非这个方法它不存在的情况下, 或者说是valueOf方法返回的也是一个对象类型,则调用toString方法

例如

var obj={    "user":"张三",    "toString":function () {       console.log('1.执行了toString()方法');        return {};    },    "valueOf":function (){        console.log('2.执行了valueOf()方法');        return " OK啦";    }}console.log(String(obj));console.log("------------------------------------------");console.log(100+obj);

结果如图

在对象进行算术运算的,时候valueOf方法会先执行,如果返回的是一个对象,那么再执行toString方法,让它去返回一个可正常被运算的基本数据类型!

var obj={    "user":"张三",    "toString":function () {       console.log('1.执行了toString()方法');        return 500;    },    "valueOf":function (){        console.log('2.执行了valueOf()方法');        return {};    }}console.log(100+obj);

结果如图

注意:如果toString方法和vlaueOf方法都返回对象类型,那么就可能会报错了

也就是说,如果都不能返回一个基本数据类型的值,

那么浏览器可能会抛出一个错误信息Uncaught TypeError: Cannot convert object to primitive value 意思是: 未捕获的TypeError:无法将对象转换为字符串值

所以说很多新手朋友刚刚遇到的就是这个对象字符串时候出现的这些问题,不知道如何解决!

小结

对象字符串中的:toString()与valueOf() 这两个方法 调用的顺序是先调用toString,不满足需求才会调用valueOf(),这里的需求是指toString方法必须返回基本的数据类型,也就是字符、数值、布尔值、undefined、null这些数据类型,也只有返回出这些数据类型之后String()函数才能正常执行字符串的转换

如果返回一个对象,就会判定使用valueOf()方法来继续处理,而valueOf()方法返回的值也必须是那些基本数据类型,如果还是返回出一个对象数据类型,则最终会判定无法进行转换!

总之: toString()方法已经返回了一个基本数据类型,就不会再调用valueOf()方法了,除非当toString()方法返回一个对象的时候,才会再次调用valueOf()方法 或者是对象进行了算术运算、对象转数值之类的操作,才会优先调用valueOf方法!

这里还说一点,这不光是对象转字符串,会启动这两个方法对象转数值,也会 大家可以自己去试试看执行顺序

以上我觉得就是对toString与valueOf方法最基本的认知, 下面我来详细说一下,这两个方法存在于什么地方!

toString与valueOf来自于哪里?

有些人肯定会问,这两个方法来自于哪里呢?别着急,我们先来复习复习什么是原型对象

原型对象prototype

还记得我以前讲过的原型对象吗? 不记得也没关系,我们来看回忆回忆!

当我们创建一个构造函数 或者 是一个普通函数的时候, 浏览器中的JS解析器都会向函数中添加一个属性名为prototype, 它就指向构造函数原型对象

例如

function 函数名() {   //this.prototype={}  相当于这样子}

你也可以理解为在 JS构造函数或者普通函数都有一个特殊的隐藏属性 prototype,

这个prototype属性对应着一个对象, 而这个对象就是我们所谓的原型对象,并且这个原型对象是对应于相应的构造函数所创建的!

如图

从上图中可以看出,每一个构造函数/普通函数其实都对应着自己的一个原型对象

并且可以使用构造函数.prototype来进行访问, 现在想起来了吧!

原型链中的toString与valueOf

接下来我们再来说说原型链,因为这里就是寻找toString与valueOf方法的关键了!

原型链还记得吧! 以前我也说过,如果有不明白的,一定要翻看前面的文章哦! 嘿嘿嘿...

图片官网地址:
http://www.mollypages.org/tutorials/js.mp

如图

这么大一张图,估计肯定吧你看得脑壳疼、精神萎靡不振、感觉身体被掏空的样子了吧,嘿嘿嘿!

关于原型链之前的文章中我说的很清楚,还没有明白的朋友赶紧去看看!

这里我主要说一下toString和valueOf这两个方法存在于哪里!

注意到图中的Object.prototype了吗?这两个方法就存在于Object原型对象当中

你不信?让我们使用Chrome浏览器调试工具来看看吧, 你完全可以使用以下代码进行查看!

console.log(Object.prototype);

如图

所以toString()与valueOf() 这两个方法 默认情况下存在于Object()原型对象当中!

那么现在我们回过头 又来看看最开始我们在做数据类型转换时,强制转换调用toString()方法

代码

function test(a,b){    console.log("Hello world");    return true;}console.log(test.toString());

这里结合原型链就很好的说明了一点,Function对象Object原型对象当中继承来的toString方法,所以可以这样子调用,绕了一圈,现在明白了吧!

结果

那么结果就是你所到的, 一个函数对象的 toString 方法会返回一个表示函数源代码的字符串, 其中包括 function关键字,形参列表,大括号,以及函数体中的内容,都是以字符串直接返回

并且普通实例对象也因为原型链的关系,继承了这两个方法,所以也可以进行调用!

toString与valueOf方法的使用总结

说这么多,这两个方法到底有什么用呢?

其实这两个方法,我个人理解就是用来限制你在javascript中操作对象或者函数的规则

举个栗子

当你把两个对象相加 obj1 + obj2,或者相减 obj1 - obj2,或者使用打印弹框 alert(obj) 打印时会发生什么?

这种操作到底合理吗? 其实从JavaScript语言设计的角度上来说, 这样是不允许运算符函数或者对象进行这样的处理方式, 与其他的语言不同,JavaScript本身是无法实现让对象进行加法或其他运算的!

因为你觉得在JS中这样有意义吗?

例如

var user = {"username": "John"};function test(){}alert(user+test);console.log(user+test);

效果如下

所以说在此类运算的情况下,对象会被自动转换为基本数据类型,然后对这些基本数据类型进行运算,并得到运算结果,必须是一个基本数据类型, 所以说toString与valueOf方法就是用来干这事情的!

毕竟这是一个js内部重要的限制因为如果遇见类似于对象的运算结果不能是另一个对象,在JS中是无法实现这种操作的!

因此从技术上无法实现此类运算,所以在实际项目中不存在让js对象进行一些数学运算, 如果真发现有,通常是写错了!

从本质上讲toString与valueOf方法就是在以下几种这种场景才使用:

  1. 当我们把对象进行数学运算的时候,帮我们返回出应该能让JS计算的基本数据类型
  2. 当我们把对象转换成字符串的时候,帮我们返回出应该能让JS计算的基本数据类型

之前我说提到的隐式数据类型转换本质就是调用了valueOf或者toString方法, 又因为原型链的缘故那么在JS中,几乎每一种类型的对象都有一个toString和valueOf这两个方法!

我们可以通过重写来测试则两个方法,在构造函数原型中重写也可以,在实例对象中直接重写也是可以的!

如果真的遇见两个js对象要进行算术运算,那么代码如下:

var user1 = {    "username": "张三",    "age":30,    toString:function (){        //alert(1);  测试执行顺序        return this.age;    },    valueOf:function (){        //alert(2);  测试执行顺序        return user1;    }}function Person(){    this.username="李四";    this.age=30;}Person.prototype.toString=function (){    return this.age;}Person.prototype.valueOf=function (){    return Person;}var user2= new Person();console.log("结果为:"+(""+user1));console.log("结果为:"+(1+user1));console.log("结果为:"+(user1+user2));

效果

为什么结果是这样呢?我们一个一个的来解释一下:

console.log("结果为:"+(""+user1));

分析

首先我们确定这是一个让对象做数学运算表达式对吧, 是一个空字符串加上一个user1实例对象 那么此时此刻,根据我们之前说的toString和valueOf执行顺序,就会自动的先执行valueOf方法,但是它又返回的是对象,那就继续执行toString方法 最后toString方法返回出来的是一个基本数据类型,也就所谓的原始值 这样就可以进行外面的运算了, 相当于返回出的表达式就为"" + 30 最后得到的结果就是一个字符串30

console.log("结果为:"+(1+user1));

这里最后返回的结果为31 大致流程就跟上面的差不多,只是最后返回出来的结果30还加上了1而已, 最后结果就为数值类型31

console.log("结果为:"+(user1+user2));

这里我们大致可以判断出来是两个对象在执行加法运算,那么必然就会自动执行 两个对象中的valueOf方法,而这里我让valueOf返回的是一个对象,就会分别让让toString方法自动执行,所以返回出来的都是基本数据类型的年龄属性值,最后相加而已,结果为数值60 大致流程就是这样!

小结

所以如果真要让对象进行这种运算,那么首先要执行的流程就是如下:

  1. 对象被转换为原始值,也就是基本数据类型
  2. 然后再进行进一步计算,把返回的基本数据类型被进一步进行数据类型转换之后,执行正常的计算!

还有就是要注意一下valueOf和toString的执行顺序

这里因为数算术运算需要得到一个原始值,那么尝试调用valueOf()再调用toString()无论哪个存在,如果没有重写,则会调用Object中默认的valueOf或toString方法!

那么如果你真的明白了,那你猜猜下面这样做会输出什么?这就留给大家了, 很简单!

代码如下

let test= ()=>{}test.valueOf = () => {    return 100}test.toString = () => {    return '不准你继续这样干下去!'}console.log(+test);console.log(test*2);console.log(test==100);console.log(test=="100");console.log(test==="100");

最后

看完之后你应该对valueOf和toString有一个基本的认识了吧!

我们在ES6valueOf和toString也可以使用Symbol.toPrimitive来代替,以后会详细给大家介绍!

给大家一点简单的练习题 考考你们!!

说出以下代码输出什么结果:

面试题1

var obj = {    i: 1,    valueOf: function () {        return this.i+100;    },    toString: function () {        return this.i;    }}var Person = {    i: +obj,    valueOf: function () {        return this.i+200;    },    toString: function () {        return this.i;    }}alert(Person);

面试题2

let a = {};let b = {};a.toString = function() {    return 1;};b.valueOf = function() {    return "1";};let sum = a + b;console.log(sum);





大家的支持就是我坚持的动力!

如果文章对你有帮助的话就请

点赞 ✍️评论 收藏

一键三连哦!

如果以上内容有任何错误或者不准确的地方,欢迎在下面 留个言指出!

或者你有更好的想法,欢迎一起交流学习❤️