乐趣区

关于javascript:全面分析toString与valueOf并随手解决掉几道大厂必备面试题

Hello,大家好,我是李白~~

最近深圳的天气是变化多端的,时而滂沱大雨,时而艳阳高照,多心愿能有个几天是连绵不绝地下雨,那该多好啊~~

让人恐怖的夏天兴许就过来了,没人晓得你在这个夏天、这个往年产生什么,然而我晓得。

晓得你是致力的,也有可能是迟缓的。不过面对秋天,面对行将到来的国庆小假期,还请持续致力吧,川流不息的不止生命,还有工夫,加油~~

明天,我是作者,也是读者,为本人交作业,也为技术学习的路上交作业~~

好了,进入正题,先了解这两个办法是做什么的:

基本上,所有 JS 数据类型都领有这两个办法,null 除外。它们俩是位于原型链上的办法,也是为了解决 javascript 值运算与显示的问题。

valueOftoString 简直都是在呈现操作符 (+-*/==><) 时被调用(隐式转换)。

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)  // 3
console.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())   // 我有这么多钱:100
console.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偏差于显示。

  1. 在进行对象转换时,将优先调用 toString 办法,如若没有重写 toString,将调用 valueOf 办法;如果两个办法都没有重写,则按 ObjecttoString输入。
  2. 在进行 强转字符串类型 时,将优先调用 toString 办法,强转为数字时优先调用 valueOf
  3. 应用运算操作符的状况下,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,难堪到我不想写进去了~~

面试题剖析

以下几道大厂必考的面试题,完满呈现出 toStringvalueOf 的作用。

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); // 1
add(1)(2);  // 3
add(1)(2)(3);// 6
add(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)                // 1
add(1)(2)              // 3
add(1)(2)(3)         // 6
add(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)    // 16
add(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 全栈我的项目~ 面向小白的完满零碎~
退出移动版