一文搞懂==中的隐式类型转换🔥🔥

前言

众所周知javascript是一种弱类型语言。强类型和弱类型主要是站在变量类型处理的角度进行分类的。强类型是一旦指定数据类型,如果不经过强制转换,那么将永远是指定的这个类型。js中无法声明数据类型,变量类型是根据实际值决定的,由编译器自动调用转换函数进行转换,这种方式称之为隐式转换,再js中能造成隐式转换的操作符有很多,比如==, +, *, /, -等,今天主要详细讲解==中的隐式类型转换。

js中的数据类型

基础数据类型

  1. string字符串类型
  2. number数字类型
  3. undefined类型
  4. null类型
  5. boolean布尔类型
  6. symbol类型(es6)
  7. bigint类型(es6)

引用数据类型

  1. object类型
  2. array类型
  3. function类型

==中的隐式类型转换

先上结论图:

  1. 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

  1. 调用对象身上的valueOf方法,如果该方法返回基础数据类型,则返回该基础类型值。
  2. 如果valueOf方法返回不是基础数据类型,则调用对象身上的toString方法,如果返回基础数据类型值则返回。
  3. 如果toString方法也没有返回基础数据类型值,则抛出TypeError异常.

type值为string

  1. 调用对象身上的toString方法,如果该方法返回基础数据类型,则返回该基础类型值。
  2. 如果toString方法返回不是基础数据类型,则调用对象身上的valueOf方法,如果返回基础数据类型值则返回。
  3. 如果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++
  }
}
  1. [] == ![]

该式子比较结果为true,因为![]为false,那么转化为[]==false,而[]转化为初始值为'' 也就是''==false,再将两边同时转化为数字0==0为true. 3. [null] == ![]

该式子也为true,因为[null]转化为初始类型值也是'',也就是''==false,再将两边同时转化为数字0==0为true.

如有错误,欢迎指出!

相关推荐
Martin -Tang2 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
SRY122404193 小时前
javaSE面试题
java·开发语言·面试
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
不二人生6 小时前
SQL面试题——连续出现次数
hive·sql·面试
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui7 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui
尝尝你的优乐美7 小时前
vue3.0中h函数的简单使用
前端·javascript·vue.js