类型分类
JS中的类型分为两大类: primitive(基本数据类型) 和 object(复杂数据类型) 。
其中primitive类型的数据有7种,分别为: Null, Undefined, Number, String, Boolean, Symbol, BigInt。
不是primitive类型的值,都可以被认为是object类型
当object类型和primitive类型进行运算的时候,JS会尝试将object类型的数据转换为primitive类型的数据后再进行相应的运算
在ECMAScript文档,为object定义了一个内部方法toPrimitive,当JS需要将尝试将对象数据类型转换为基本数据类型的时候,就会调用这个方法
在ES6中,为引用数据类型提供了Symbol.toPrimitive属性,我们可以通过该属性来重写object的内部方法toPrimitive
toString
将一个对象转换为对应的字符串形式,该方法返回一个表示该对象的字符串
toString被定义在了Object方法的显示原型对象上
const num = 123
const bool = true
const str = 'Klaus'
const sym = Symbol('sym')
console.log(typeof num.toString(), num.toString()) // => string '123'
console.log(typeof bool.toString(), bool.toString()) // => string 'true'
console.log(typeof str.toString(), str.toString()) // => string 'Klaus'
console.log(typeof sym.toString(), sym.toString()) // string 'Symbol(sym)'
当对象调用 toString 方法时,默认返回的格式是 [object Type],其中:
- 第一个
object是固定的,表示这是一个对象。 - 第二个
Type是对象的内部[[Class]]属性的值,这通常是对象的类型描述,而不是它的构造函数。
对于一些特殊的对象,如果函数,数组等都重写了自己的toString方法
const obj = {}
const arr = [1, 2, 3]
const fun = () => {}
const err = new Error('我是错误信息')
const date = new Date()
console.log(typeof obj.toString(), obj.toString()) // => string [object Object]
// 数组输出的是arr.join(',') ---- 如果是空数组,返回的就是空字符串
console.log(typeof arr.toString(), arr.toString()) // => string 1,2,3
// 函数返回函数体本身
console.log(typeof fun.toString(), fun.toString()) // => string () => {}
// 错误对象 输出message属性值
console.log(typeof fun.toString(), fun.toString()) // => string "Error: 我是错误信息"
// 日期对象输出字符串日期格式
console.log(typeof date.toString(), date.toString())
// => string Fri Nov 05 2021 13:57:12 GMT+0800 (中国标准时间)
toString是最精确的类型判断方式之一
console.log(toString.call('')) // => [object String]
console.log(toString.call(22)) // => [object Number]
console.log(toString.call(undefined)) // => [object Undefined]
console.log(toString.call(null)) // => [object Null]
console.log(toString.call(new Date)) // => [object Date]
console.log(toString.call(Math)) // => [object Math]
console.log(toString.call(globalThis)) // => [object global]
console.log(toString.call(()=>{})) // => [object Function]
console.log(toString.call({})) // => [object Object]
console.log(toString.call([])) // => [object Array]
console.log(toString.call(new Set())) // => [object Set]
console.log(toString.call(new Map())) // => [object Map]
Symbol.toStringTag
在对象没有重写Object.prototype.toString方法的情况下, 对象调用toString方法时,返回结果为[object Object]
对此 JS提供了一个内置的symbol值 Symbol.toStringTag,通过实现这个函数,我们可以修改对象的内部 [[Class]] 属性的值
const user = {
name: 'Klaus',
age: 23,
// Symbol.toStringTag 是一个get访问器函数
// getter需要返回字符串类型值,如果不是字符串类型值则静默失效而不是进行隐式类型转换
get [Symbol.toStringTag]() {
return this.name
}
}
console.log(user.toString()) // => [object Klaus]
valueOf
返回当前对象的原始值。返回当前对象所对应的基本数据类型值
如果当前对象无法获取到对应的基本数据类型,那么会将对象本身原封不动的返回
valueOf被定义在了Object方法的显示原型对象上
// valueOf --- 如果能返回原始值,就返回原始值
const num = 123
const bool = true
const str = 'Klaus'
console.log(num.valueOf()) // => 123
console.log(bool.valueOf()) // => true
console.log(str.valueOf()) // => Klaus
// valueOf --- 如果无法返回原始值,就返回参数对象本身
const obj = {}
const arr = [1, 2, 3]
const fun = () => {}
console.log(obj.valueOf()) // => {}
console.log(arr.valueOf()) // => [ 1, 2, 3 ]
console.log(fun.valueOf()) // => [Function: fun]
// Date对象 重写了valueOf方法 --- 返回时间所对应的时间戳
const date = new Date()
console.log(date.valueOf()) // => 1636092865039
toPrimitive
在ECMAScript文档,为object定义了一个内部方法toPrimitive, 其会在进行类型转换或者运算的时候被调用。
// toPrimitive的伪代码形式如下:
toPrimitive(target, PreferredType = 'default': 'string' | 'number')
-
如果没有定义PreferredType, 则默认为
default「default的表现行为和number完全一致 」 -
执行流程
-
PreferredType 为
number:- 优先调用
valueOf,若返回基本类型则使用该值。 - 否则调用
toString。 - 还不能转换,直接报错
- 优先调用
-
PreferredType 为
string:- 优先调用
toString,若返回基本类型则使用该值。 - 否则调用
valueOf。 - 还不能转换,直接报错
- 优先调用
-
[Symbol.toPrimitive]
通过实现 Symbol.toPrimitive,我们可以完全控制对象到基本类型的转换。
如果实现了Symbol.toPrimitive方法,JS就不会再去调用原生的valueOf和toString
class Foo {
constructor(num) {
this.num = num;
}
// hint的值和规则和toPrimitive的参数规则完全一致
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return this.num;
}
if (hint === 'string') {
return String(this.num);
}
return null;
}
}
const foo = new Foo(10);
console.log(Number(foo)); // 10
console.log(String(foo)); // "10"
// 比较运算和加法运算的时候,hint的值是default,并不是number
console.log(foo == 10) // => false == 10 -> false
console.log(foo + 2) // => false + 2 -> 0 + 2 -> 2
console.log(foo + '2') // => false + '2' -> 'false2'
// 全等判断 和 转布尔值时,不会调用Symbol.toPrimitive
console.log(foo === '10') // => false
console.log(!!foo) // => true
某些对象会重写Object原型上的toString和valueOf方法
const dateValueOf = Date.prototype.valueOf
const dateToString = Date.prototype.toString
// 添加Date的valueOf日志
Date.prototype.valueOf = function () {
console.log('date valueOf')
return dateValueOf.call(this)
}
// 添加Date的toString日志
Date.prototype.toString = function () {
console.log('date toString')
return dateToString.call(this)
}
const date = new Date()
// Date在进行加法运算和判等运算时,优先执行toString方法
console.log(date + 1)
console.log(date == 2)
特殊判等
如何让 a === 1 && a === 2 && a === 3 的结果为 true
class A {
constructor(value) {
this.value = value;
}
valueOf() {
return this.value ++
}
}
const a = new A(1)
console.log(a == 1 && a == 2 && a == 3) // => true
如何让 a == 1 && a == 2 && a == 3 的结果为 true
let target = {
a: 0
}
let proxy = {}
Object.defineProperty(proxy, 'a', {
get() {
target.a++
return target.a
}
})
console.log(proxy.a===1 && proxy.a===2 && proxy.a===3) // => true
let target = {
a: 0
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
if (key === 'a') {
target[key]++
return Reflect.get(target, key, receiver)
}
}
})
console.log(proxy.a===1 && proxy.a===2 && proxy.a===3) // => true