1.前言
arduino
console.log([1] == 1); // true
console.log([1] == '1'); // true
小伙们,蒙对了么?是不是脑袋里面一个大大的问号。
2.什么是隐式类型转换
上面的面试题虽然简简单单两行,包含的知识点其实就是我们前端程序员都熟悉的隐式类型转换
隐式类型转换是指编译器自动完成类型转换的方式,总是期望转成基本类型的值
3.触发隐式类型转换的场景
- 算术运算符(
+
,-
,*
,/
,%
) - 关系运算符<、>、 <=、 >= 、== 、!=等
- 逻辑运算符!、&&、||
- 条件语句if、while、三目运算符
- 属性键遍历,比如for...in
- 模板字符串
${}
今天重点说一下双等号的基础类型的隐式转换 ,和对象类型的隐式转换
4.基础类型的隐式转换
4.1 ==比较基础类型的隐式转换

js
// null 和 undefined
console.log(null == undefined); // true
console.log(null == 0); // false
console.log(undefined == ''); // false
// 数字 vs 字符串
console.log(0 == ''); // true ('' → 0)
console.log(1 == '1'); // true ('1' → 1)
console.log(1 == '1.0'); // true
console.log(1 == '1abc'); // false ('1abc' → NaN)
// 布尔值 vs 其他
console.log(true == 1); // true (true → 1)
console.log(false == 0); // true (false → 0)
console.log(true == '1'); // true (true→1, '1'→1)
console.log(false == ''); // true (false→0, ''→0)
// 特殊情况
console.log(NaN == NaN); // false
console.log([] == ![]); // true (![] → false → 0, [] → '' → 0)
其他能够引发基础类型的隐式转换的场景还有很多,后面继续补充
5. 对象隐式转换规则
对象隐式转换主要三要素: Symbol.toPrimitive 、Object.prototype.valueOf() 、Object.prototype.toString()
- 如果对象定义了Symbol.toPrimitive方法,那会优先调用,无视valueOf()和toString()方法
- 如果对象未定义Symbol.toPrimitive方法,那会根据期望的基本类型进行隐式转换,如果期望类型是string,就会先调用obj.toString(),如果没有得到原始值,则继续调用obj.valueOf()方法,返回原始值
- 如果对象未定义Symbol.toPrimitive方法,那会根据期望的基本类型进行隐式转换,如果期望类型是number,就会先调用obj.valueOf(),如果没有得到原始值,则继续调用obj.toString()方法,返回原始值
- 如果对象未定义Symbol.toPrimitive方法,期望值是基础类型(比如:string),同时toString()和valueOf都没有返回原始值,会直接报错
javascript
const obj = {
age: 10,
valueOf() {
return this
},
toString() {
return this
}
}
console.log(obj + '') //Cannot convert object to primitive value
5.1 Symbol.toPrimitive
The
Symbol.toPrimitive
well-known symbol specifies a method that accepts a preferred type and returns a primitive representation of an object. It is called in priority by all type coercion algorithms.-来自MDN理解:Symbol.toPrimitive是可以接受编译器指定的期望基本类型并返回对象原始值的方法。在转换基本类型过程中,总是被优先调用。
Symbol.toPrimitive有一个参数hint,hint在规范协议里面默认是'default',有三个可选值:'string'、'number'、'default'。hint会根据期望值去选定参数
5.1.1 触发hint是string的情况
- window.alert(obj)
- 模板字符串:
${obj}
- 成为的对象属性:test[obj] = obj1
javascript
const obj = {
[Symbol.toPrimitive](hint){
if(hint === 'number') {
return 10
} else if(hint === 'string') {
return 'hello'
}
return true
}
}
window.alert(obj) // hello
console.log(`${obj}`) // hello
obj[obj] = 1
console.log(Object.keys(obj)) //['hello']
5.1.2 触发hint是number的情况
- 一元+、位移运算
- -减法、*乘法、/除法、关系运算符(<、>、 <=、 >= 、== 、!=)
- Math.pow、String.prototype.slice等内部方法
javascript
const obj = {
[Symbol.toPrimitive](hint){
if(hint === 'number') {
return 10
} else if(hint === 'string') {
return 'hello'
}
return true
}
}
console.log('一元+:',+obj) //一元+: 10
console.log('位移:',obj >> 0) //位移: 10
console.log('减法:',5-obj) //减法: -5
console.log('乘法:',5*obj) //乘法: 50
console.log('除法:',5/obj) //除法: 0.5
console.log('大于:',5>obj) //大于: false
console.log('大于等于:',5>=obj) //大于等于: false
console.log('Math.pow:',Math.pow(2,obj)) //Math.pow: 1024
console.log('内部方法:','abcdefghijklmnopq'.slice(0,obj)) //内部方法: abcdefghij
5.1.3 触发hint是default的情况
- 二元+
- ==、!=
javascript
const obj = {
[Symbol.toPrimitive](hint){
if(hint === 'number') {
return 10
} else if(hint === 'string') {
return 'hello'
}
return true
}
}
console.log(5 + obj) //6
console.log(5 == obj) //false
console.log(0 != obj) // true
首先会返回default时候的原始值,若没有再默认调用vlaueOf(除了Date对象)
javascript
const obj = {
[Symbol.toPrimitive](hint){
if(hint === 'number') {
return 10
} else if(hint === 'string') {
return 'hello'
}
// return true
}
}
console.log(obj.valueOf()) //NaN
console.log(1+obj) //NaN
5.2 Object.prototype.valueOf
在未定义Symbol.toPrimitive方法时候,期望类型是number,所以会直接执行valueOf()方法
javascript
const obj = {
age: 10,
name: '小明',
toString() {
return this.name
},
valueOf() {
return this.age
}
}
console.log(+obj) // 10
若是定义的valueOf方法返回的不是原始值,会继续执行toString方法
javascript
const obj = {
age: 10,
name: '小明',
toString() {
return this.name
},
valueOf() {
return this
}
}
console.log(+obj) // NaN
5.3 Object.prototype.toString
在未定义Symbol.toPrimitive方法时候,期望类型是string,所以会直接执行toString()方法
javascript
const obj = {
age: 10,
name: '小明',
toString() {
return this.name
},
valueOf() {
return this.age
}
}
console.log(`${obj}`) // 小明
obj[obj] = 1
console.log(Object.keys(obj)) //[ 'age', 'name', 'toString', 'valueOf', '小明' ]
若是定义的toString方法返回的不是原始值,会继续执行valueOf方法
javascript
const obj = {
age: 10,
name: '小明',
toString() {
return this
},
valueOf() {
return this.age
}
}
console.log(`${obj}`) // 10
若是没有定义toString方法,会继续执行原型上的toString的方法返回原始值
javascript
const obj = {
age: 10,
name: '小明',
// toString() {
// return this.name
// },
valueOf() {
return this.age
}
}
console.log(`${obj}`) // [object Object]
//如果将原型上的toString重置,就会去执行valueOf方法
Object.prototype.toString = undefined
console.log(`${obj}`) // 10
6. 特殊对象Date
hint是default的时候,会优先调用toString,,然后在调用valueOf。
hint已经表明期望值是string或者number的时候,还是按照期望的进行转换
sql
const date = new Date()
console.log('date:toString',`${date}`) //date:toString Mon Dec 05 2022 21:53:18 GMT+0800 (中国标准时间)
console.log('date:valueOf',+date) //date:valueOf 1670248398283
console.log('date:default',date + 1) //date:default Mon Dec 05 2022 21:53:18 GMT+0800 (中国标准时间)1
7. 总结
看完文章知道这个面试题的原因了么?
lua
console.log([1] == 1); // true [1] -> '1' -> 1
console.log([1] == '1'); // true [1] -> '1'
console.log([1] == 1)
- 触发对象隐式类型转换的期望值是 number,但是数组继承了
Object.prototype.valueOf
的默认行为,是返回对象本身 - 上一步结果不是原始值会继续调用 toString方法,从 [1] 得到字符串类型 '1'
- 基础类型的隐式转换,数字与字符串类型进行比较,则字符串类型转数字,从 '1' 转成 数字类型 1
- 最后类型相同值相同
1 == 1
,返回为 true
- 触发对象隐式类型转换的期望值是 number,但是数组继承了
console.log([1] == '1')
- 触发对象隐式类型转换的期望值是 number,但是数组继承了
Object.prototype.valueOf
的默认行为,是返回对象本身 - 上一步结果不是原始值会继续调用 toString方法,从 [1] 得到字符串类型 '1'
- 基础类型的隐式转换,字符串与字符串类型进行比较,类型相同值相同
'1' == '1'
,返回为 true
- 触发对象隐式类型转换的期望值是 number,但是数组继承了