当前位置: 首页 > 科技观察

综合分析toString和valueOf,解决几道大厂面试必答题

时间:2023-03-22 15:24:18 科技观察

基本上JS数据类型都有这两个方法,null除外。它们是位于原型链上的方法,也是用来解决javascript值计算和显示的问题。当存在运算符(+-*/==<)时,几乎总是会调用valueOf和toString(隐式转换)。toString返回表示对象的字符串,当对象表示为文本值或以预期的字符串方式引用时,将自动调用toString方法。1.手动调用看看效果如何。嗯,和介绍的一样,不骗人,都是转成字符串。比较特殊的是,表示对象时,变成了[objectObject],表示数组时,变成了用逗号连接的数组内容的字符串,相当于Array.join(',')。leta={}letb=[1,2,3]letc='123'letd=function(){console.log('fn')}console.log(a.toString())//'[objectObject]'console.log(b.toString())//'1,2,3'console.log(c.toString())//'123'console.log(d.toString())//'function(){console.log('fn')}'2。最精准的类型判断这是一种更精准的判断方式,在某些场合比使用typeof&instanceof更高效准确。toString.call(()=>{})//[objectFunction]toString.call({})//[objectObject]toString.call([])//[objectArray]toString.call('')//[objectString]toString.call(22)//[objectNumber]toString.call(undefined)//[objectundefined]toString.call(null)//[objectnull]toString.call(newDate)//[objectDate]toString.call(Math)//[objectMath]toString.call(window)//[objectWindow]3、什么时候自动调用?使用运算符时,如果其中一个是对象,会先调用toSting方法,即隐式转换,然后再进行。letc=[1,2,3]letd={a:2}Object.prototype.toString=function(){console.log('Object')}Array.prototype.toString=function(){console.log('Array')returnthis.join(',')//返回toString的默认值(下面测试)}Number.prototype.toString=function(){console.log('Number')}String.prototype.toString=function(){console.log('String')}console.log(2+1)//3console.log('s')//'s'console.log('s'+2)//'s2'console.log(c<2)//false(once=>'Array')console.log(c+c)//"1,2,31,2,3"(twice=>'Array')控制台。log(d>d)//false(twice=>'Object')4.重写toString方法既然我们知道toString有一个默认的方法,我们也可以重写这个方法classA{constructor(count){this.count=count}toString(){return'我有这么多钱:'+this.count}}leta=newA(100)console.log(a)//A{count:100}console.log(a.toString())//我有这么多钱:100console.log(a+1)//我有这么多钱:1001Nice.valueOf返回当前对象的原始值。具体功能与toString类似,也有上述自动调用和重写的方法。这里没什么好说的,主要是两者的区别,请继续阅读🙊🙊letc=[1,2,3]letd={a:2}console.log(c.valueOf())//[1,2,3]console.log(d.valueOf())//{a:2}两者的共同区别:输出对象时会自动调用。区别:默认返回值不同,有优先级关系。两者并存时,数值运算先调用valueOf,字符串运算先调用toString。看代码就知道了:classA{valueOf(){return2}toString(){return'hahaha'}}leta=newA()console.log(String(a))//'hahaha'=>(toString)console.log(Number(a))//2=>(valueOf)console.log(a+'22')//'222'=>(valueOf)console.log(a==2)//true=>(valueOf)console.log(a===2)//false=>(严格等于不会触发隐式转换)结果给人的感觉是如果转换为字符串时调用了toString方法,如果转换为调用valueOf方法时的值。但是a+'22'很不和谐,字符串组合应该调用toString方法。为了弄清真相,我们需要更严格的实验。暂时去掉valueOf方法classA{toString(){return'hahaha'}}leta=newA()console.log(String(a))//'hahaha'=>(toString)console.log(Number(a))//NaN=>(toString)console.log(a+'22')//'哈哈哈22'=>(toString)console.log(a==2)//false=>(toString)移除toString方法看classA{valueOf(){return2}}leta=newA()console.log(String(a))//'[objectObject]'=>(toString)console.log(Number(a))//2=>(valueOf)console.log(a+'22')//'222'=>(valueOf)console.log(a==2)//true=>(valueOf)发现有点不一样吧?!它不像上面的toString那样统一。那个[objectObject],我猜是继承自Object,我们去掉看看。classA{valueOf(){return2}}leta=newA()Object.prototype.toString=null;console.log(String(a))//2=>(valueOf)console.log(Number(a))//2=>(valueOf)console.log(a+'22')//'222'=>(valueOf)console.log(a==2)//true=>(valueOf)总结:valueOf偏向于计算,toString偏向于显示。在进行对象转换时,会先调用toString方法。如果toString没有被覆盖,将调用valueOf方法;如果两个方法都没有重写,则按照Object的toString输出。当强制转换为字符串类型时,首先调用toString方法,当转换为数字时,首先调用valueOf方法。使用算术运算符时,valueOf的优先级高于toString。[Symbol.toPrimitive]MDN:Symbol.toPrimitive是一个内置的Symbol值,作为对象的函数值属性存在。当一个对象被转换成对应的原始值时,这个函数就会被调用。你有点困惑吗???把它当作一个函数就好了~~作用:同valueOf()和toString(),但是优先级比这两个高;调用该函数时,会传递一个字符串参数hint,表示当前操作Mode的取值,一共有三种模式:classA{constructor(count){this.count=count}valueOf(){return2}toString(){return'哈哈哈'}//我来了[Symbol.toPrimitive](hint){if(hint=="number"){return10;}if(hint=="string"){return"HelloLibai";}returntrue;}}consta=newA(10)console.log(`${a}`)//'HelloLibai'=>(hint=="string")console.log(String(a))//'HelloLibai'=>(hint=="string")console.log(+a)//10=>(hint=="number")console.log(a*20)//200=>(hint=="number")console.log(a/20)//0.5=>(hint=="number")console.log(Number(a))//10=>(hint="number")console.log(a+'22')//'true22'=>(hint=="default")console.log(a==10)//false=>(hint=="default")是特殊的(+)拼接字符,这个属于默认模式。划重点:此方法不兼容IE,尴尬到不想写了~~面试题解析以下各大厂商必考的面试题,完美呈现toString和valueOf的功能。1.a===1&&a===2&&a===3为true双等号(==):会触发隐式类型转换,所以可以使用valueOf或者toString来实现。每次判断都会触发valueOf方法,同时让value+1,这样下一次判断才能成立。classA{constructor(value){this.value=value;}valueOf(){returnthis.value++;}}consta=newA(1);if(a==1&&a==2&&a==3){console.log("HiLibai!");}Congruent(===):严格等于没有隐式转换,这里使用Object.defineProperty数据劫持的方法实现letvalue=1;Object.defineProperty(window,'a',{get(){returnvalue++}})if(a===1&&a===2&&a===3){console.log("HiLibai!")}上面我们在全局窗口上劫持了a,每次a做判断的时候每次都会触发get属性获取值,每次获取到值都会触发函数进行自增,判断3次后自增3次,所以公式会最终成立。注:defineProperty可以参考这篇文章学习,点我进入传送门来自:大厂面试题分享:如何让(a===1&&a===2&&a===3)的值为真?2.实现一个无限累加函数问题:用JS实现一个无限累加函数add,例子如下:add(1);//1add(1)(2);//3add(1)(2)(3);//6添加(1)(2)(3)(4);//10//以此类推最后一次调用return;}returnsum;//返回一个函数}add(1)//1add(1)(2)//3add(1)(2)(3)//6add(1)(2)(3)(4)//10add函数内部定义了sum函数并返回,实现了sum函数的不断调用形成一个闭包,每次调用累加值,然后返回当前函数sumadd()会返回每次sum一个函数,直到最后一个没有被调用,默认会触发toString方法,所以我们这里重写toString方法,返回累加的最终值a。可以这样理解:add(10):执行函数add(10),返回sum函数。注意这次并没有调用sum,默认执行的是sum.toString方法。所以输出10;add(10)(20):执行函数add(10),返回sum(此时a为10),再执行sum(20),此时a为30,返回sum,最后调用sum.toString()输出30.add(10)(20)...(n)等等。3.Currying实现多参数累加这里是上述累加的升级版,实现了多参数传递累加。add(1)(3,4)(3,5)//16add(2)(2)(3,5)//12functionadd(){//1将所有参数转换成数组letargs=Array.prototype.slice.call(arguments)//2再次调用add函数,传递合并的当前和之前的参数letfn=function(){letarg_fn=Array.prototype.slice.call(arguments)returnadd.apply(null,args.concat(arg_fn))}//最终默认调用3,返回合并后的值{letargs=[...arguments];letfn=function(){returnadd.apply(null,args.concat([...arguments]))}fn.toString=()=>args.reduce((a,b)=>a+b)returnfn;}