JavaScript深入ToPrimitive类型转换(非常全 建议收藏❗❗❗)

举个例子进行引入

js 复制代码
console.log(({a:1,b:2}).toString());
console.log(Object.prototype.toString.call({a:1}));

console.log(true == '2'); 
console.log(+ []);
console.log([] + []);

可能对于大佬一眼道出其中的缘由,但是还是诚邀继续阅读下去,或许下面有可以一起探讨的内容。 如果你不确定自己的答案,这里就先卖个关子,后面正文还会讲解到。

之前有文章写过了"简单数据类型直接相互转换",想要更全面学习JavaScript类型转换,可以去Look。下面主要介绍object转成简单数据类型与隐式类型转换

01| 开胃菜:包装类

js 复制代码
var a = 1.123
console.log(a);
var b = new Number(a)
console.log(typeof b);
b.toFixed(2) // 1.00 两位小数 
a.toFixed(2) // 1.12 两位小数 自动装修 包装类

问:可能对于对象b有toFixed方法,比较合情理,但是对于简单类型a(返回原始值)发现有点说不通。当然,平时使用不会这样去考虑像这样很多的简单类型为什么有那么多内置函数?

这里简单谈谈包装类

当你尝试对这些原始值调用方法时,JavaScript会临时创建一个对应的包装对象,以便你可以访问和调用该类型的方法。这个过程可以被称为 "自动包装"或"临时包装"

例如,当你对变量a调用toFixed(2)时,JavaScript引擎会在后台做类似下面的事情:

  1. 创建一个Number对象的临时实例,并将a的值赋给这个新对象。
  2. 调用这个临时Number对象上的toFixed(2)方法。
  3. 方法执行完毕后,销毁这个临时对象。 需要注意的是,这种临时包装不会改变原始值本身;它们只是提供了访问原型链上方法的途径。

02| ToPrimitive 开始表演

1,Boolean

js 复制代码
// 返回对象
console.log(Boolean(new Boolean(false))); // true
console.log(Boolean({},[])); // true

虽然给出答案,但是还是值得深入了解执行过程。

  • new Boolean(false) 创建了一个 Boolean 对象,即使是 false,这个对象本身在布尔上下文中也被视为 true
  • Boolean(...) 全局函数用于将任何值转换为布尔值。对于对象(包括数组),只要它们不是 nullundefined,就会被视为 true
  • 多参数处理Boolean 函数只会考虑第一个参数,后续的参数会被忽略。

【注意】通常情况下,不使用 new Boolean()new Number()new String() 来创建包装对象,因为这容易导致混淆和意外行为。例如,在比较操作中,包装对象和原始值之间的比较可能会产生意想不到的结果。推荐直接使用原始的布尔值、数字和字符串,并利用全局的 BooleanNumberString 函数来进行类型转换。

例如:

ini 复制代码
let boolValue = false;
console.log(Boolean(boolValue)); // false

let obj = {};
console.log(Boolean(obj)); // true

2,String

回到文章开头的问题,下面进行分别解释。 在JavaScript中,每个对象都有一个继承自Object.prototypetoString方法。这个方法用于返回一个表示该对象的字符串。然而,直接调用对象上的toString方法和使用Object.prototype.toString.call()会有不同的行为和输出。让我们来详细解释一下:

1. 调用对象的 toString 方法
less 复制代码
console.log(({a:1, b:2}).toString()); // 输出: [object Object]
  • 行为 :当你直接在一个对象上调用toString方法时(如上例中的匿名对象),它实际上调用了该对象从Object.prototype继承的toString方法。
  • 输出 :默认情况下,这个方法会返回"[object Object]",这表明这是一个对象,但并不提供关于对象内部结构或属性的具体信息。对于大多数内置对象类型(如数组、日期等),它们通常会覆盖这个方法以提供更有意义的字符串表示。
2. Object.prototype.toString.call()

console.log(Object.prototype.toString.call({a:1})); // 输出: [object Object]

  • 行为Object.prototype.toString.call(obj) 是一种更可靠的方法来获取对象的类型信息。这里,call 方法允许你指定this的值为传递给它的参数obj,因此它可以在任何对象上调用Object.prototypetoString方法,而不会受到对象自身是否覆盖了toString方法的影响。

  • 输出 :它总是返回一个格式为[object Type]的字符串,其中Type是对象的实际内部类型标签。例如:

    • 对于普通对象,它是[object Object]
    • 对于数组,它是[object Array]
    • 对于日期对象,它是[object Date]
    • 对于函数,它是[object Function]
    • ...

小结toString

  • 直接调用 :想象你有一个盒子,上面只写着"盒子",但是你不知道里面装的是什么。直接调用对象的toString方法就像只是告诉你这个东西是一个"盒子",但没有提供更多细节。
  • 使用 Object.prototype.toString.call() :这就好比你有一个特殊的工具,可以打开任何类型的盒子,并准确地告诉你里面是什么------比如"玩具盒"、"书盒"或者"食品盒"。这种方法能够更精确地告诉你对象的类型,而不仅仅是说它是一个对象。
实际应用

Object.prototype.toString.call()常被用来进行类型检测,因为它能给出比typeof运算符更具体的信息。例如,typeof []typeof {} 都返回"object",但这并不能区分数组和普通对象。而使用Object.prototype.toString.call()则可以明确地区分它们:

javascript 复制代码
console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(function(){})); // [object Function]

这种方法提供了更加细粒度的类型检查,尤其在处理复杂数据结构或需要确保类型安全的情况下非常有用。

3,引入valueOf

前面其实讲过toString方法,相当ToPrimitive(传入input,字符串string),尝试将传入值转成字符串。

下面再将valueOf方法提出,并且谈谈两者区别,记住重点哦~~ 后面二元运算符+进行隐私转换我们还要重点运用的

valueOf 方法是 JavaScript 中每个对象都继承的一个方法,它用于返回一个对象的原始值。这个方法在类型转换时特别重要,尤其是在将对象转换为基本类型(如数字或字符串)时。valueOftoString 是两个常用的钩子方法,JavaScript 引擎会在特定情况下调用它们来决定如何表示对象。

valueOftoString 的选择顺序

当 JavaScript 需要将对象转换为原始值时,它遵循以下规则:

  1. valueOf 优先 :首先尝试调用 valueOf 方法。如果 valueOf 返回的是一个原始值(如数字、字符串、布尔值等),则使用该值。
  2. toString 备选 :如果 valueOf 没有返回原始值(即返回了一个对象),则尝试调用 toString 方法。如果 toString 返回的是一个原始值,则使用该值。
  3. 抛出错误 :如果两者都没有返回原始值,则抛出一个 TypeError
示例解析
ini 复制代码
var arr = [1, 2, 3];
console.log(arr.valueOf()); // [1, 2, 3]
  • 对于数组,valueOf 返回数组本身。因此,arr.valueOf() 的输出就是 [1, 2, 3]
javascript 复制代码
var date = new Date(2024, 12, 18);
console.log(date.valueOf()); // 1703155200000 (毫秒数)
  • 对于日期对象,valueOf 返回从1970年1月1日(UTC)到当前日期的毫秒数。因此,date.valueOf() 输出的是对应的毫秒数。
javascript 复制代码
let specialObj = {
    valueOf: function () {
        console.log('valueOf.....');
        return 123;
    },
    toString: function () {
        console.log('toString.....');
        return '456';
    }
};
console.log(specialObj + 1); // 124
console.log(Number(specialObj)); // 123
console.log(String(specialObj)); // 456

object.prototype 原型链上面有toString,valueOf方法。这里进行重写方法。 当然可能你会问__proto__上面也会有sting方法吧

下面一句话区分:proto 是关于"谁是我的父亲",而 prototype 则是关于"我将成为谁的父亲"

回来分析ToPrimitive(input,string)

  • specialObj + 1 :当你尝试将 specialObj 与数字相加时,JavaScript 会首先尝试调用 valueOf 方法来获取对象的数值表示。如果 valueOf 返回的是一个原始值(如数字),则使用该值进行计算。在这个例子中,valueOf 返回了 123,所以 specialObj + 1 等于 '124'。
  • Number(specialObj) :当你使用 Number 构造函数(或一元加号 +)将对象转换为数字时,JavaScript 也会优先调用 valueOf 方法。如果 valueOf 返回的是一个原始值,则使用该值;否则,再尝试调用 toString 方法。这里 valueOf 返回 123,所以 Number(specialObj) 的结果是 '123'
  • String(specialObj) :当你使用 String 构造函数(或隐式转换为字符串)时,JavaScript 会优先调用 toString 方法。如果 toString 没有被定义或没有返回原始值,则会尝试调用 valueOf。在这个例子中,toString 返回了 456,所以 String(specialObj) 的结果是 '456'
如果valueOf 返回为this ,结果又不一样

因为这时候,判断valueof返回this,指向对象specialobj

对于specialObj + 1 调用valueof方法后返回对象"object Object" ,不是原始值。接着调用toString 尝试转成字符串,得到'456'字符串。遇到"+"进行二元运算符计算(下文详细讲),把"456"与1进行拼接,得到"4561"【相当遇到有字符串,优先进行字符串拼接,而不是计算 + 】

其他两种一样进行分析

03| 一元运算符 +


js 复制代码
console.log(+"1");

知道隐式转换 的大佬就一眼知道,使用了ToNumber操作,相当Number('1')。但是调用这个方法前进行隐式转换,返回number类型的1

那下面的尼?

js 复制代码
console.log(+{} )
console.log(+ ['1']); 
console.log(+ ['1', '2']); 

当然,可能你抓一下头发就能想出去来,也好奇能不能总结,套"模板"进行快速判断。

当输入的值是对象的时候,先调用 ToPrimitive(input, Number) 方法,执行的步骤是:

  • 1,如果obj 是简单类型,直接输出

  • 2,否则,调用valueOf方法,如果输出是原始值,返回

  • 3,否则,调用toString()方法,如果是原始值,返回

  • 4,否则,返回还是对象,JavaScript报错

    +{} 为例,对象不是简单类型。调用valueOf方法,输出空对象{},因为不是原始值,toString()方法,返回"[object Object]"。

    得到返回值,然后调用ToNumber方法,"[object Object]"输出NaN。这个过程就是"{} => {} => [object Object] => NaN" 另外的一样的进行分析,结果

js 复制代码
console.log(+ ['1']); // 1 只要一个数,不带","
console.log(+ ['1', '2']); // NaN

04| 二元运算符 +


1 + '1' 我们知道答案是 '11',那 null + 1[] + [][] + {}{} + {} 呢?

如果要了解这些运算的结果,不可避免的我们要从规范下手。

规范地址:es5.github.io/#x11.6.1

这里引用羽神的JavaScript深入之头疼的类型转换(下) 里面总结的。

当计算 value1 + value2时:

  1. lprim = ToPrimitive(value1)
  2. rprim = ToPrimitive(value2)
  3. 如果 lprim 是字符串或者 rprim 是字符串,那么返回 ToString(lprim) 和 ToString(rprim)的拼接结果
  4. 返回 ToNumber(lprim) 和 ToNumber(rprim)的运算结果

下面看一下,几种使用二元运算符情况

1.null 与数字

例如null + 1,按照前面规范进行分析: 1.lprim = ToPrimitive(null) 因为null 为简单类型,直接返回,lprim = null 2.rprim = ToPrimitive(1) 同样,rprim = 1 3.lprim 和rprim 都不是字符串,都使用ToNumber方法进行运算

这里ToNumber(null) 的结果为0,ToNumber(1) 的结果为 1

所以,null + 1 相当于 0 + 1,最终的结果为数字 1

接下来看个难一点的

2.数组和数组

js 复制代码
console.log([1,2] + []); 

同样使用规范进行分析:

  • 1.lprim = ToPrimitive([1,2]))[1,2]是数组,相当于ToPrimitive([1,2], Number),先调用valueOf方法,返回对象本身,因为不是原始值,调用toString方法,返回字符串12
  • 2.rprim = ToPrimitive([])[]是数组,相当于ToPrimitive([], Number),先调用valueOf方法,返回对象本身,因为不是原始值,调用toString方法,返回空字符串""
    1. lprim和rprim都是字符串,执行拼接操作

(补充:两端all数字,使用加法运算; any 非数字,使用字符串拼接)

更复杂的是数组和对象 对于== 进行判断,可以参照羽神的总结 讲的很全。在这里我毫不吝啬推荐

(小声bb,JavaScript类型转换表面很简单,但是底层深滴很。大厂面试官会问的比较多)阅读后有异议,欢迎评论区探讨。大佬轻喷~~

相关推荐
东离与糖宝5 分钟前
webScoke api 介绍
前端
群联云防护小杜9 分钟前
黑客如何找到App中的源IP:原理与防范
运维·服务器·前端·网络·网络协议·tcp/ip
AI大模型训练家12 分钟前
JAVA没有搞头了吗?
java·开发语言·python·面试·职场和发展·性能优化
Αиcíеиτеǎг1 小时前
基于前端技术UniApp和后端技术Node.js的电影购票系统
前端·uni-app·node.js
licy__1 小时前
Vue 3 中页面跳转方式的详细介绍
前端·javascript·vue.js
前端热爱者1 小时前
vue-element-admin npm install 安装失败,tui-editor更名导致
前端·vue.js·npm·tui-editor
excel1 小时前
创意标题匹配问题
前端
bug管理者1 小时前
14:00面试,14:06就出来了,问的问题过于变态了。。。
软件测试·面试·职场和发展
tester Jeffky1 小时前
深入探索Vue.js中的class绑定:动态样式管理的核心机制
前端·javascript·vue.js
wsdf6gu1 小时前
**掌握文本切分:在自然语言处理中的应用技巧**
前端