面试经典头疼问题:JavaScript中的类型转换[2]—对象到原始值的转换机制

序言

在上篇文章中我们了解了原始数据类型之间的相互转换规则以及原始数据类型转换成对象的规则,它们就像JS转换规则中的基石,我们今天要讲的是对象到原始值的转换机制,最后就要利用到原始数据类型之间的相互转换机制。那么话不多说,开始我们今天的内容吧~

首先,在聊对象到原始值转换机制前我们还需要搞明白两个类型转换方法:valueOf()toString()

valueOf()

在JavaScript中,valueOf 方法通常用于包装对象(Wrapper Objects)中,其中包括 NumberStringBoolean。这些包装对象是由 JavaScript 自动创建的,以便处理基本数据类型和对象之间的转换。valueOf 方法被设计为返回原始数据类型的值,以便在需要时进行自动转换。

需要注意的是,valueOf 方法并非只在包装对象中有用。你可以在自定义对象上实现 valueOf 方法,以便在需要时定义对象的原始值。这样的行为通常是为了更好地控制对象的转换行为。

1. Number 对象:

Number 对象表示数字值,并有一个 valueOf 方法,用于返回原始数值。

javascript 复制代码
let numObj = new Number(42);
console.log(numObj.valueOf()); // 输出 42

2. String 对象:

String 对象表示字符串,并有一个 valueOf 方法,用于返回原始字符串。

javascript 复制代码
let strObj = new String("Hello");
console.log(strObj.valueOf()); // 输出 "Hello"

3. Boolean 对象:

Boolean 对象表示布尔值,并有一个 valueOf 方法,用于返回原始布尔值。

javascript 复制代码
let boolObj = new Boolean(true);
console.log(boolObj.valueOf()); // 输出 true

toString()

其实引用数据类型转字符串就是调用原型上Object.prototype.toString()方法,但是这个方法对于不同类型的数据进行了分类处理:

  1. {}.toString() 返回由 "[Object" 和 class 和 "]" 组成的字符串

  2. [].toString() 返回由数组内部元素以逗号拼接的字符串

  3. xx.toString() 直接返回字符串字面量

1. Object.toString()

默认情况下,如果对象没有覆盖 toString 方法,它会继承Object.prototype.toString 方法的行为,返回一个由 [object 和对象的 class 属性值] 组成的字符串

javascript 复制代码
{}.toString() // 返回 "[object Object]"

2. Array.toString()

数组对象的 toString 方法返回数组内部元素以逗号拼接的字符串。

javascript 复制代码
[1, 2, 3].toString() // 返回 "1,2,3"

3. xx.toString()

对象可以定义自己的 toString 方法,以覆盖默认行为。

javascript 复制代码
const customObject = {
  toString: function() {
    return "Custom Object";
  }
};

customObject.toString() // 返回 "Custom Object"

ToPrimitive(Object数据类型转原始数据类型)

在JavaScript中,ToPrimitive 是一个抽象操作,用于将一个值转换为对应的原始值。原始值可以是字符串、数字或布尔值。ToPrimitive 的行为是在某些操作中定义的,例如使用 == 运算符进行比较、调用 Object.prototype.toString 方法等。

ToPrimitive 操作是由 ECMAScript规范(ES5官方文档) 定义的:

ToPrimitive Conversions官方文档

不得不说,有没有一种官方文档能人性化一点,为了让文档无懈可击,可是真折磨读者,所以大家不必纠结官方文档的文字多么苦涩难懂,我给大家取其精华,去其糟粕完如下:

ToPrimitive(obj, Number)

当我们希望将对象转换为数字时,ToPrimitive(obj, Number) 的操作步骤如下:

  1. 如果参数 obj 是基本数据类型,直接返回该值,因为基本数据类型已经是原始值。
  2. 否则,调用对象的 valueOf 方法。如果 valueOf 返回原始值,则直接返回。
  3. 如果 valueOf 方法未返回原始值,则调用对象的 toString 方法。如果 toString 返回原始值,则直接返回。
  4. 如果 toString 方法也未返回原始值,则抛出错误,因为无法将对象转换为数字。

ToPrimitive(obj, String)

当我们希望将对象转换为字符串时,ToPrimitive(obj, String) 的操作步骤如下:

  1. 如果参数 obj 是基本数据类型,直接返回该值,因为基本数据类型已经是原始值。
  2. 否则,调用对象的 toString 方法。如果 toString 返回原始值,则直接返回。
  3. 如果 toString 方法未返回原始值,则调用对象的 valueOf 方法。如果 valueOf 返回原始值,则直接返回。
  4. 如果 valueOf 方法也未返回原始值,则抛出错误,因为无法将对象转换为字符串。

对象转布尔值

在 JavaScript 中,对象在布尔上下文中被视为 true。因此,对于 ToPrimitive(obj, Boolean) 操作,直接返回 true

总结

md 复制代码
- ToPrimitive(obj,Number)  ==> Number()
1. 如果参数obj是数据基本类型,直接返回
2. 否则,调用 `valueOf` 方法,如果得到原始值,则返回 
3. 否则,调用 `toString` 方法,如果得到原始值,则返回
4. 否则,报错


- ToPrimitive(obj,String)  ==> String()
1. 如果参数obj是数据基本类型,直接返回
2. 否则,调用 `toString` 方法,如果得到原始值,则返回
3. 否则,调用 `valueOf` 方法,如果得到原始值,则返回 
4. 否则,报错

# 对象转布尔就是true
- ToPrimitive(obj,Boolean)  ==> 直接True

对象转Number

来到了这里,如果前面的步骤你已经读懂了,其实这一步只是总结或者说是引擎显示执行的一步,我们如果想让对象转Number类型,只需要调用Numebr()方法则行,但是引擎隐式执行的步骤其实还有valueOf()以及ToPrimitive(obj,Number),其中ToPrimitive()方法我们是无法显示使用的,下面我们来对引擎执行的底层机制来进行一个总结梳理吧~

To Number Conversions

我们来看ES5官方文档中对ToNumber方法的定义:

To Number Conversions官方文档

  1. 首先通过ToPrimitive方法使对象类型转换成基本数据类型

  2. 然后再利用ToNumber方法使原始数据类型转换成Number类型。

举一反三 结合实战

一元运算符

在 JavaScript 中,一元加号操作符 + 不仅仅用于数学运算,它还可以用于将一个数据转换为Number类型,我们其实已经知道它的转换过程了,那么让我们通过一些例子来深入了解一元加号操作符在不同情境下的行为吧。

转换字符串为数字

javascript 复制代码
console.log(+'1'); // Number('1')

在这个例子中,一元加号操作符将字符串 '1' 转换为数字。这其实就是执行了正常的原始数据类型相互转换中的字符串转Number

空数组转换为数字

javascript 复制代码
console.log(+ []); // 0

这里对空数组使用一元加号会尝试将数组转换为数字,首先就是通过ToPrimitive方法使对象类型转换成基本数据类型,我们已经知道会将这个数组转换成字符串类型,空数组转换成字符出为'0',最后转换为数字类型结果为数字 0

空对象转换为数字

javascript 复制代码
console.log(+ {}); // NaN

这里对空对象使用一元加号会尝试将对象转换为数字,首先就是通过ToPrimitive方法使对象类型转换成基本数据类型,我们已经知道会将这个对象转换成字符串"[object Object]",最后转换为数字类型结果为数字 NaN

尝试将包含元素的数组转换为数字

arduino 复制代码
javascriptCopy code
console.log(+ [1,2,3]); // NaN

对包含元素的数组使用一元加号同样会尝试将数组转换为数字。由于数组中有多个元素,通过ToPrimitive方法将其转换为基本数据类型时会变成字符串"1,2,3",最后转换成数字类型为NaN

二元运算符

二元运算符其实也是遵循讲左右两边的数转为Number类型进行计算,但是当加号两边有字符串出现时则按字符串进行拼接。

js 复制代码
console.log(1 + '1'); // 11

console.log(1 + null); // 1

console.log([] + {}); // [object Object]
  1. console.log(1 + '1');
  • 由于 1 是数字,而 '1' 是字符串,当它们相加时,会发生字符串拼接而不是数学运算。结果是字符串 '11'
  1. console.log(1 + null);
  • 当数字和 null 进行加法运算时,null 会被转换为数字 0。因此,结果是数字 1
  1. console.log([] + {});
  • 对于空数组 [] 和空对象 {},JavaScript 会尝试将它们转换为字符串并进行字符串拼接。由于数组和对象的默认 toString 方法返回的是字符串 """[object Object]",所以最终结果是字符串 "[object Object]"

进阶

还有一些经典题目让我们来看看:

js 复制代码
console.log(NaN == NaN); //false

console.log(true == 1); //true

console.log(1 == {} );  //false

console.log({} == {});  //false

这些代码的解释如下:

  1. console.log(NaN == NaN);
  • 在 JavaScript 中,NaN(Not a Number)是一个特殊的数值,官方文档规定它与任何值,包括自身,进行比较都会返回 false。因此,NaN == NaN 返回 false 是符合规范的行为。
  1. console.log(true == 1);
  • 在这里,布尔值 true 会被转换为数字 1。因此,这个比较返回 true
  1. console.log(1 == {});
  • 在这里,空对象 {} 会被转换为字符串 "[object Object]",然后再转换为数字NaN。因此,这个比较相当于 1 == 'NaN',最终返回 false
  1. console.log({} == {});
  • 在这里,两个空对象进行比较。然而,对象之间的比较不会比较它们的内容,而是比较它们在内存中的引用地址。因此,两个独立的空对象的比较会返回 false

回到面试题

到了尾声,我们再回过头来看我们刚开始抛出的那道有趣且复杂的问题,你现在是否能够解答这道题并且说明思路?

js 复制代码
console.log([] == ![]);

这道题的输出结果就是true,接下来我们来理清楚引擎的执行过程。

步骤:

  1. 看到!号,先将对象[]转换成Boolean类型值为,![]的结果为false

  2. []false 都转换成基本数据类型

  3. 然后也就可以写成 console.log('' == 0)

  4. 最后都转换成Number类型,也就可以写成console.log(0 == 0),结果为true

总结

那么到了这里,我们这两次关于JS中类型转换规则就讲完了,第一篇文章很基础但是很重要,因为我们发现,JS引擎进行类型转换的最底层逻辑其实就是将复杂数据类型一直尝试转换成原始数据类型再进行比较。 这是这两次内容汇总的笔记总结:

md 复制代码
# 基本数据类型的转换
1. 转布尔值 只有字符串和数字可以转换成布尔值为true 并且需要有实际的值
2. 转数字
3. 转字符串
4. 转对象


# 对象转原始值

- 转字符串 调用的其实就是Object.prototype.toString()
1. {}.toString() 返回由 "[Object" 和 class 和 "]" 组成的字符串
2. [].toString() 返回由数组内部元素以逗号拼接的字符串
3. xx.toString() 直接返回字符串字面量  

- valueOf
用于转换包装类 只对包装类中的三种原始数据类型对象转换成原始数据类型有效

# ToPrimitive(Object数据类型转原始数据类型)

- ToPrimitive(obj,Number)  ==> Number()
1. 如果参数obj是数据基本类型,直接返回
2. 否则,调用 `valueOf` 方法,如果得到原始值,则返回 
3. 否则,调用 `toString` 方法,如果得到原始值,则返回
4. 否则,报错


- ToPrimitive(obj,String)  ==> String()
1. 如果参数obj是数据基本类型,直接返回
2. 否则,调用 `toString` 方法,如果得到原始值,则返回
3. 否则,调用 `valueOf` 方法,如果得到原始值,则返回 
4. 否则,报错

# 对象转布尔就是true
- ToPrimitive(obj,Boolean)  ==> 直接True

# 一元运算符 +
一元加号操作符 `+` 不仅仅用于数学运算,它还可以用于将一个数据转换为`Number`类型。
# 二元运算符 +
lprim + rprim

ToPrimitive(v1) + ToPrimitive(v2)
1. 当+两边有一个是字符串则按字符串进行拼接
2. 否则,转到 number 进行计算

结语

那么到了这里我们今天的文章就结束啦~

创作不易,如果感觉这个文章对你有帮助的话,点个赞吧♥

更多内容:面试官:能不能手写浅拷贝和深拷贝?其实只要搞懂引用类型的存储方式

博主的开源Git仓库 欢迎收藏: gitee.com/cheng-bingw...

相关推荐
ZJ_.7 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营12 分钟前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood37 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端39 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_8543 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
程序猿进阶43 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
还是大剑师兰特1 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248941 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5