前言
众所周知javascript是一种弱类型语言。强类型和弱类型主要是站在变量类型处理的角度进行分类的。强类型是一旦指定数据类型,如果不经过强制转换,那么将永远是指定的这个类型。js中无法声明数据类型,变量类型是根据实际值决定的,由编译器自动调用转换函数进行转换,这种方式称之为隐式转换,再js中能造成隐式转换的操作符有很多,比如==, +, *, /, -等,今天主要详细讲解==中的隐式类型转换。
js中的数据类型
基础数据类型
- string字符串类型
- number数字类型
- undefined类型
- null类型
- boolean布尔类型
- symbol类型(es6)
- bigint类型(es6)
引用数据类型
- object类型
- array类型
- function类型
==中的隐式类型转换
先上结论图:
- null与undefined类型在==中进行比较时,这两种类型一直都返回true
ini
null == undefined // true
null == null // true
undefined == undefined // true
但是当这两种类型中的任意类型与其它类型==比较时都会返回false
ini
null == 123 // false
null == {} // false
null == [] // false
null == '' // false
2.当==两边类型相同时,如果两边的值都是基础类型时直接比较他们的原始值是否相同,如果两边的值都是引用类型时直接比较他们的引用地址是否相同.
ini
'aaa' == 'aaa' // true
'aaa' == 'bbb' // false
const obj = {};
obj == {} // false 引用地址不同
obj == obj // true
3.当==两边的类型不同时,就需要js进行隐式类型转换。如果两边都是基础数据类型 ,则会调用ToNumber(input)将值转换为数字 进行比较。如果一边是基础数据类型一边是引用数据类型,引用数据类型则会调用ToPrimitive()将引用类型值转化为原始值,转化为原始类型值后借助上述规则进行比较。
看到这里的小伙伴肯定又有疑惑了,什么是ToNumber,什么又是ToPrimitive?下面对这两个抽象操作进行讲解。
ToPrimitive(input, type?)
ToPrimitice是JavaScript引擎内部的一个抽象操作,将参数input转换成原始值,如果传入的参数原本就是初始值则直接返回。否则会根据type的不同有不同的表现。
type值为number
- 调用对象身上的valueOf方法,如果该方法返回基础数据类型,则返回该基础类型值。
- 如果valueOf方法返回不是基础数据类型,则调用对象身上的toString方法,如果返回基础数据类型值则返回。
- 如果toString方法也没有返回基础数据类型值,则抛出TypeError异常.
type值为string
- 调用对象身上的toString方法,如果该方法返回基础数据类型,则返回该基础类型值。
- 如果toString方法返回不是基础数据类型,则调用对象身上的valueOf方法,如果返回基础数据类型值则返回。
- 如果valueOf方法也没有返回基础数据类型值,则抛出TypeError异常.
不传递type参数
不传递type参数时,对于Date日期对象,则相当于type为string的情况,而其他对象相当于type为number的情况。
再==比较时,对象调用ToPrimitive函数进行转化时就属于不传递type参数的情况。
那valueOf()和toString()又是什么呢?接下来再来看看这两个函数:
valueOf()
返回对象的原始值表示。valueOf是Object.prototype的方法,由Object来的对象都会有该方法,但是很多内置对象会重写这个方法,以适合实际需要。它的转化规则如下:
对象 | 转化后的值 |
---|---|
String对象 | 对应的字符串值 |
Number对象 | 对应的数字值 |
Boolean对象 | 对应的布尔值值 |
Object对象({}) | 对象本身 |
数组对象 | 数组本身 |
函数对象 | 函数本身 |
Date日期对象 | 对应的时间戳毫秒值 |
toString()
返回对象的字符串表示。toString是Object.prototype的方法,由Object来的对象都会有该方法,但是很多内置对象会重写这个方法,以适合实际需要。它的转化规则如下:
对象 | 转化后的值 |
---|---|
String对象 | 对应的字符串值 |
Number对象 | '数字' |
Boolean对象 | '布尔值' |
Object对象({}) | "[object Object]" |
数组对象 | 数组中每项用逗号分割的字符串,空数组会转化为空字符串 |
函数对象 | 函数体字符串 |
Date日期对象 | 可读的时间字符串 |
ToNumber()
ToNumber()其实就是将值转化为数字,其实就是相当于调用Number()函数。它的转化规则如下:
类型 | 转化后的结果 |
---|---|
Undefined | NaN |
Null | +0 |
Boolean | argument 为 true, return 1; argument 为 false, return 0 |
Number | return argument参数 |
String | 将字符串中的内容转化为数字(比如"23"->23)空字符串转化为数字0,如果转化失败则返回NaN(比如"23a"->NaN) |
Symbol | 抛出 TypeError 异常 |
Object | 先primValue = ToPrimitive(input, 'number'),再对primValue使用ToNumber(primValue) |
接下来就看几个例子
==两边类型不同(两边都为基础类型)
ini
'123' == 123 // true,字符串调用ToNumber转化为数字后为123
'a123' == 123 // false,字符串调用ToNumber转化为数字后为NaN
true == 1 // true, 布尔值true转化为数字为1
false == 0 // true,布尔值true转化为数字为0
true == '1' // true,布尔值true转化为数字为1,字符串调用ToNumber转化为数字后为1
false == '' // true,布尔值true转化为数字为0,空字符串转化为数字0
==两边类型不同(一边基础类型,一边引用类型),为了观察引用类型调用ToPrimitive是否如我们上边所说的,要先对Object.prototype上valueOf和toString方法进行重写
javascript
// 保存原始的valueOf
var valueOf = Object.prototype.valueOf;
var toString = Object.prototype.toString;
// 添加valueOf日志
Object.prototype.valueOf = function () {
console.log('valueOf');
return valueOf.call(this);
};
// 添加toString日志
Object.prototype.toString = function () {
console.log('toString');
return toString.call(this);
};
const obj = {};
obj == '123' // false
// {}由valueOf和toString转化后为[object Object]的字符串,
// 而等号右边也是字符串,相同类型直接比较值。
obj == 123 // false
// {}由valueOf和toString转化后为[object Object]的字符串,而等号右边是数字,
// 又转化为不同类型比较,
// 那么就会将不属于数字类型的值转化为数字类型[object Object]转化后为NaN,所以是false。
obj == '[object Object]' // true
从控制台打印可以看出再进行比较时确实是先调用了valueOf再调用toString,接下来再看看数组对象 的比较过程:同理先将Array.prototype上的valueOf和toString方法进行重写这里就不展示了.
sql
[] == 0 // true
// []先调用valueOf返回数组本身,再调用toString转化为'',之后就是''==0的比较流程
//''转化为数字0
[] == '' // true
// []先调用valueOf返回数组本身,再调用toString转化为'',之后就是''==''比较流程
[1] == 1 // true
// []先调用valueOf返回数组本身,再调用toString转化为'1',之后就是'1'==1的比较流程
//'1'转化为数字1
[1, 2] == '1,2' // true
// []先调用valueOf返回数组本身,再调用toString转化为'1,2',之后就是'1,2'=='1,2'的比较流程
[null] == 0 // true
// []先调用valueOf返回数组本身,再调用toString转化为'',之后就是''==0的比较流程
//''转化为数字0
[null, undefined] == ',' // true
// []先调用valueOf返回数组本身,再调用toString转化为',',之后就是','==','的比较流程
[] == false // true
// []先调用valueOf返回数组本身,再调用toString转化为'',之后就是''==false的比较流程
//''转化为数字0,false转化为0
[0] == false // true
// []先调用valueOf返回数组本身,再调用toString转化为'0',之后就是'0'==false的比较流程
//'0'转化为数字0,false转化为数字0
[1] == true // true
Date对象再==比较中也是先调用ToPrimitive()进行转化,不过Date对象相当于是ToPrimitive(input, 'string'),也就是先调用toString再根据情况调用valueOf.
javascript
// 添加valueOf日志
Date.prototype.valueOf = function () {
console.log('valueOf');
return 'bbb';
};
// 添加toString日志
Date.prototype.toString = function () {
console.log('toString');
return 'aaa';
};
new Date() == 'aaa' // true
// 先调用toString返回'aaa'是一个基础数据类型值,则不再去调用valueOf
// 添加valueOf日志
Date.prototype.valueOf = function () {
console.log('valueOf');
return 'bbb';
};
// 添加toString日志
Date.prototype.toString = function () {
console.log('toString');
return {};
};
new Date() == 'bbb' // true;
// 先调用toString返回{}不是一个基础数据类型值,则继续去调用valueOf,返回'bbb'。
面试题
1.如何使a == 1 && a == 2 && a == 3成立?
css
const a = {
count: 1,
valueOf() {
return a.count++
}
}
- [] == ![]
该式子比较结果为true,因为![]为false,那么转化为[]==false,而[]转化为初始值为'' 也就是''==false,再将两边同时转化为数字0==0为true. 3. [null] == ![]
该式子也为true,因为[null]转化为初始类型值也是'',也就是''==false,再将两边同时转化为数字0==0为true.
如有错误,欢迎指出!