共计 7299 个字符,预计需要花费 19 分钟才能阅读完成。
最近啊,有同学来问,数据类型转换咱们都学过,可是在面试题中遇到了咱们怎么就老是不会用啊。
讲到数据类型转换,咱们基本上都是讲到隐式数据类型转换和显式转换。讲到数据类型转换,咱们就要来聊聊,valueOf 和 toString 这两个办法了。
基本上,所有 JS 数据类型都领有这两个办法,null 除外。
这两个办法都是在原型链上的办法,为了解决 javascript 值运算与显示的问题。
valueOf 和 toString 简直都是在呈现操作符 (+-*/==><) 时被调用(隐式转换)。
toString
toString 返回一个示意该对象的字符串,当对象示意为文本值或以冀望的字符串形式被援用时,toString 办法被主动调用。
- 手动调用的成果
运行的成果,全副都转成了字符串。
比拟非凡的中央就是,在示意对象的时候,就会打印 [object Object],示意数组的时候,就打印数组内容以逗号连贯的字符串,相当于数组办法 Array.join(‘,’) 办法。
let a = {}
let b = [1, 2, 3]
let c = '123'
let d = function(){ console.log('fn') }
console.log(b.toString()) // '1,2,3'
console.log(a.toString()) // '[object Object]'
console.log(c.toString()) // '123'
console.log(d.toString()) // 'function(){ console.log('fn') }'
2. 最精准的类型判断
toString 有时候在某种场合会比应用 typeof & instanceof 判断更高效和精确些,属于更准确的判断形式
toString.call(()=>{}) // [object Function]
toString.call([]) // [object Array]
toString.call('') // [object String]
toString.call({}) // [object Object]
toString.call(undefined) // [object undefined]
toString.call(null) // [object null]
toString.call(22) // [object Number]
toString.call(new Date) // [object Date]
toString.call(Math) // [object Math]
toString.call(window) // [object Window]
3. 什么时候会主动调用
很多同学会问了,这个 toString 办法,什么时候会主动调用呢?应用操作符的时候,如果其中一边为对象,则会先调用 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 的默认值(上面测试)}
Number.prototype.toString = function(){console.log('Number')
}
String.prototype.toString = function(){console.log('String')
}
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
valueOf
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 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。
看这个解释基本上很多同学都是看不懂的,不论你有没有学过 JS。
其实不必想的太简单,就把他看成是一个函数就能够了。
- 作用:同 valueOf()和 toString()一样,然而优先级要高于这两者;
2. 该函数被调用时,会被传递一个字符串参数 hint,示意以后运算的模式
一共有三种模式:
string:字符串类型
number:数字类型
default:默认
上面来看看 Symbol.toPrimitive 在代码中的实现吧:
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 属性获取值,并且每一次获取值都会触发一次函数履行一次自增,判断三次就自增三次,所以最初会让公式成立。
- 如何实现一个有限累加函数
问题:用 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;
}
好了,明天的内容就分享那么多,不晓得对于 valueOf 和 toString 办法你有没有更加粗浅的了解了呢?
如果明天的分享教程你学到了货色,别忘了关注公众号【前端研究所】哦!平时也欢送把你不懂的问题分享到公众号文章的评论注意,说不定下一次就来解说你的疑难了呢!