Hello,大家好,我是李白~~
最近深圳的天气是变化多端的,时而滂沱大雨,时而艳阳高照,多心愿能有个几天是连绵不绝地下雨,那该多好啊~~
让人恐怖的夏天兴许就过来了,没人晓得你在这个夏天、这个往年产生什么,然而我晓得。
晓得你是致力的,也有可能是迟缓的。不过面对秋天,面对行将到来的国庆小假期,还请持续致力吧,川流不息的不止生命,还有工夫,加油~~
明天,我是作者,也是读者,为本人交作业,也为技术学习的路上交作业~~
好了,进入正题,先了解这两个办法是做什么的:
基本上,所有JS数据类型都领有这两个办法,null除外。它们俩是位于原型链上的办法,也是为了解决javascript值运算与显示的问题。
valueOf
和 toString
简直都是在呈现操作符(+-*/==><)
时被调用(隐式转换)。
toString
返回一个示意该对象的字符串,当对象示意为文本值或以冀望的字符串形式被援用时,toString办法被主动调用。
1. 手动调用看看什么成果
嗯,跟介绍的一样,没骗人,全副都转成了字符串。
比拟非凡的中央就是,示意对象的时候,变成[object Object]
,示意数组的时候,就变成数组内容以逗号连贯的字符串,相当于Array.join(',')
。
let a = {}let b = [1, 2, 3]let c = '123'let d = function(){ console.log('fn') }console.log(a.toString()) // '[object Object]'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(()=>{}) // [object Function]toString.call({}) // [object Object]toString.call([]) // [object Array]toString.call('') // [object String]toString.call(22) // [object Number]toString.call(undefined) // [object undefined]toString.call(null) // [object null]toString.call(new Date) // [object Date]toString.call(Math) // [object Math]toString.call(window) // [object Window]
3. 什么时候会主动调用呢
应用操作符的时候,如果其中一边为对象,则会先调用toSting
办法,也就是隐式转换
,而后再进行操作。
let c = [1, 2, 3]let d = {a:2}Object.prototype.toString = function(){ console.log('Object')}Array.prototype.toString = function(){ console.log('Array') return this.join(',') // 返回toString的默认值(上面测试)}console.log(2 + 1) // 3console.log('s') // 's'console.log('s'+2) // 's2'console.log(c < 2) // false (一次 => 'Array')console.log(c + c) // "1,2,31,2,3" (两次 => 'Array')console.log(d > d) // false (两次 => 'Object')
4. 重写toString
办法
既然晓得了有 toString
这个默认办法,那咱们也能够来重写这个办法
class A { constructor(count) { this.count = count } toString() { return '我有这么多钱:' + this.count }}let a = new A(100)console.log(a) // A {count: 100}console.log(a.toString()) // 我有这么多钱:100console.log(a + 1) // 我有这么多钱:1001
Nice.
valueOf
返回以后对象的原始值。
具体性能与toString
大同小异,同样具备以上的主动调用和重写办法。
这里就没什么好说的了,次要为两者间的区别,有请持续往下看????????
let c = [1, 2, 3]let d = {a:2}console.log(c.valueOf()) // [1, 2, 3]console.log(d.valueOf()) // {a:2}
两者区别
- 共同点:在输入对象时会主动调用。
- 不同点:默认返回值不同,且存在优先级关系。
二者并存的状况下,在数值运算中,优先调用了valueOf
,字符串运算中,优先调用了toString
。
看代码方可通晓:
class A { valueOf() { return 2 } toString() { return '哈哈哈' }}let a = new A()console.log(String(a)) // '哈哈哈' => (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
办法去掉
class A { toString() { return '哈哈哈' }}let a = new A()console.log(String(a)) // '哈哈哈' => (toString)console.log(Number(a)) // NaN => (toString)console.log(a + '22') // '哈哈哈22' => (toString)console.log(a == 2) // false => (toString)
- 去掉
toString
办法看看
class A { valueOf() { return 2 }}let a = new A()console.log(String(a)) // '[object Object]' => (toString)console.log(Number(a)) // 2 => (valueOf)console.log(a + '22') // '222' => (valueOf)console.log(a == 2) // true => (valueOf)
发现有点不同吧?!它没有像下面 toString
那样对立规整。
对于那个 [object Object]
,我预计是从 Object
那里继承过去的,咱们再去掉它看看。
class A { valueOf() { return 2 }}let a = new A()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
,示意以后运算的模式,一共有三种模式:- string:字符串类型
- number:数字类型
- default:默认
上面来看看实现吧:
class A { constructor(count) { this.count = count } valueOf() { return 2 } toString() { return '哈哈哈' } // 我在这里 [Symbol.toPrimitive](hint) { if (hint == "number") { return 10; } if (hint == "string") { return "Hello Libai"; } return true; }}const a = new A(10)console.log(`${a}`) // 'Hello Libai' => (hint == "string")console.log(String(a)) // 'Hello Libai' => (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")
比拟非凡的是(+)拼接符,这个属于default
的模式。
划重点:此办法不兼容IE,难堪到我不想写进去了~~
面试题剖析
以下几道大厂必考的面试题,完满呈现出 toString
与 valueOf
的作用。
1. a===1&&a===2&&a===3 为 true
双等号(==):会触发隐式类型转换
,所以能够应用 valueOf
或者 toString
来实现。
每次判断都会触发valueOf
办法,同时让value+1
,能力使得下次判断成立。
class A { constructor(value) { this.value = value; } valueOf() { return this.value++; }}const a = new A(1);if (a == 1 && a == 2 && a == 3) { console.log("Hi Libai!");}
全等(===):严格等于不会进行隐式转换
,这里应用 Object.defineProperty
数据劫持的办法来实现
let value = 1;Object.defineProperty(window, 'a', { get() { return value++ }})if (a === 1 && a === 2 && a === 3) { console.log("Hi Libai!")}
下面咱们就是劫持全局window
下面的a
,当a
每一次做判断的时候都会触发get
属性获取值,并且每一次获取值都会触发一次函数履行一次自增,判断三次就自增三次,所以最初会让公式成立。
- 注:
defineProperty
可参考这篇文章学习,点我进入传送门 - 自:大厂面试题分享:如何让(a===1&&a===2&&a===3)的值为true?
2. 实现一个有限累加函数
问题:用 JS 实现一个有限累加的函数 add
,示例如下:
add(1); // 1add(1)(2); // 3add(1)(2)(3); // 6add(1)(2)(3)(4); // 10 // 以此类推
function add(a) { function sum(b) { // 应用闭包 a = b ? a + b : a; // 累加 return sum; } sum.toString = function() { // 只在最初一次调用 return a; } return sum; // 返回一个函数}add(1) // 1add(1)(2) // 3add(1)(2)(3) // 6add(1)(2)(3)(4) // 10
add
函数外部定义sum
函数并返回,实现间断调用sum
函数造成了一个闭包,每次调用进行累加值,再返回以后函数sum
add()
每次都会返回一个函数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. 柯里化实现多参累加
这里是下面累加的升级版,实现多参数传递累加。
add(1)(3,4)(3,5) // 16add(2)(2)(3,5) // 12
function add(){ // 1 把所有参数转换成数组 let args = Array.prototype.slice.call(arguments) // 2 再次调用add函数,传递合并以后与之前的参数 let fn = function() { let arg_fn = Array.prototype.slice.call(arguments) return add.apply(null, args.concat(arg_fn)) } // 3 最初默认调用,返回合并的值 fn.toString = function() { return args.reduce(function(a, b) { return a + b }) } return fn}// ES6写法function add () { let args = [...arguments]; let fn = function(){ return add.apply(null, args.concat([...arguments])) } fn.toString = () => args.reduce((a, b) => a + b) return fn;}
总结
小白写文,如写得不好,还请给个倡议 如果对你有帮忙的话,还请点个赞
更多信息分享:
- 不会写作的程序猿不是一个好程序猿
- 一个基于vuepress的前端学习笔记,记录,只是为了更好的摸鱼
- Vue、Nuxt服务端渲染、NodeJS全栈我的项目~面向小白的完满零碎~