yuanyuanbyte / Blog

圆圆的博客,预计写七个系列:JavaScript深入系列、JavaScript专题系列、网络系列、Webpack系列、Vue系列、JavaScript基础系列、HTML&CSS应知应会系列。
311 stars 126 forks source link

JavaScript 基础系列之原始值转换的抽象操作 toPrimitive #75

Open yuanyuanbyte opened 2 years ago

yuanyuanbyte commented 2 years ago

本系列的主题是 JavaScript 基础,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末

如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。

JS原始值转换的抽象操作 toPrimitive

前言

Symbol.toPrimitive 是一个内置的抽象操作,它是作为对象的函数值属性存在的,当一个对象转换为对应的 原始值 时,会调用此函数。

注意:这里涉及到原始值的概念,所以在详细讲解toPrimitive之前,我们会介绍一下 什么是原始值

让我们开始吧~

js的原始值和引用值

在ECMAScript中,变量可以存放两种类型的值,即原始值引用值

原始值(primitive value)

原始值是固定而简单的值,是存放在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。

最新的 ECMAScript 标准定义了7 种原始值:undefinedBooleanNumberStringBigIntSymbolnull

其实在今年的早些时候,null 还不属于原始类型。

引用值(reference value):

引用值则是比较大的对象,存放在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(pointer),指向存储对象的内存处。

所有引用类型都集成自Object

如果一个值是引用类型的,那么它的存储空间将从堆中分配。由于引用值的大小会改变,所以不能把它放在栈中,否则会降低查询速度。相反,存放变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存放在栈中对变量性能无任何负面影响。如图:

在这里插入图片描述

我们看一下MDN对原始值的定义:

原始值( primitive values )

Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”

js原始值转换的抽象操作 toPrimitive

Symbol.toPrimitive 是一个内置的抽象操作,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。

该函数被调用时,会被传递一个字符串参数,表示要转换到的原始值的预期类型。参数的取值是 "number""string""default" 中的任意一个。

toPrimitive 转换规则如下:

如果传入参数是string,也就是对象到字符串的转换,经过了如下步骤:

在这里插入图片描述

如果传入参数是number/default,也就是对象到数字的转换,经过了如下步骤:

和上面有点不同,到数字的转换会先尝试使用valueOf()方法

注意:对于所有非日期对象来说,对象到原始值的转换基本上是对象到数字的转换

日期对象的特殊情况

日期对象在原型里自定义了toString(),即Date.prototype.toString()

1 var date = new Date();
2 date.toString(); // => "Mon Dec 28 2015 21:58:10 GMT+0800 (中国标准时间)"

Date 对象覆盖了从 Object 继承来的Object.prototype.toString() 方法。Date的toString() 方法总是返回一个美式英语日期格式的字符串。当一个日期对象被用来作为文本值或用来进行字符串连接时,toString()方法会被自动调用。

"+" 和 "==" 应用的对象到原始值的转换包含日期对象一种特殊情形

日期类是JavaScript语言核心中唯一的预先定义类型,它定义了有意义的向字符串和数字类型的转换。

对于所有非日期的对象来说, 对象到原始值的转换基本上是对象到数字的转换(首先调用valueOf()) , 日期对象则使用对象到字符串的转换模式,然而,这里的转换和上文讲述的井不完全一致:通过valueOf或toString()返回的原始值将被直接使用,而不会被强制转换为数字或字符串。

和"==" 一样, "<” 运算符以及其他关系运算符也会做对象到原始值的转换, 但要除去日期对象的特殊情形:任何对象都会首先尝试调用valueOf(), 然后调用toString()。不管得到的原始值是否直接使用,它都不会进一步被转换为数字或字符串。

"+"、 "=="、 "!=" 和关系运算符是唯一执行这种特殊的字符串到原始值的转换方式的运算符。 其他运算符到特定类型的转换都很明确,而且对日期对象来讲也没有特殊情况。 例如 " - ,, (减号)运算符把它的两个操作数都转换为数字。

下面的代码示例:

var now = new Date();    // 当前时间
typeof (now + 1);         // "string"
typeof (now - 1);       // "number"
now == now. toString(); // true
now > (now -1);         // true
>var now = new Date();  
>now + 1
>'Fri Oct 01 2021 10:58:55 GMT+0800 (中国标准时间)1'
>now - 1
>1633057135763

image.png

字符串连接符与算术隐式转换规则混淆

console.log(1 + true);      //2
console.log(1 + "true");        //"1true"
console.log(1 + undefined);     //NaN
console.log(1 + null);      //1

+两边有一边是字符串,那这个+就是字符串连接符,它会把其他数据类型调用String()方法转成字符串然后拼接;

+做为算术运算符会把其他数据类型调用Number()转成数字然后做加法运算;

布尔值true会被转换数字 1

undefined会被转换为 NaN

null会转换为数字 0

实例详解

//大坑
console.log ( [] == 0 );        //true
console.log ( ! [] == 0 );      //true
//神坑
console.log ( [] == ! [] );     //true
console.log ( [] == [] );       //false
//史诗级坑
console.log({} == !{});     //false
console.log({} == {});      //false

[]0比较:

(1)[].valueOf().toString() 得到空字符串

(2)Number("") == 0 成立

![]0比较:

(1)逻辑非优先级高于关系运算符 ![] = false (空数组转布尔得到true,然后取反得到false)

(2)false == 0 成立

[]![]比较:

(1) [].valueOf().toString() 得到空字符串 ""

(2) ![] = false

(3) Number("") == Number(false) 成立 都是0

[][]比较:

引用类型数据存在堆内存中,栈内存中存储的是地址,所以他们的结果是false

{}!{}比较:

(1) {}.valueOf().toString() 得到字符串'[object Object]'

(2) !{} = false

(3) Number('[object Object]') == Number(false) 不成立,因为转换到最后 是NaN0比较,所以结果为 false

{}{}比较:

引用类型数据存在堆内存中,栈内存中存储的是地址,所以他们的结果是false

>{}+[]
>0

空对象加空数组就不一样了,加号运算符的定义是这样的:如果其中一个是字符串,另一个也会被转换为字符串,否则两个运算数都被转换为数字。 而同时,javascript有这样的特性,如果{}既可以被认为是代码块,又可以被认为是对象字面量,那么js会把他当做代码块来看待。

这就很好解释了,{}被当做了代码块,只有+[],根据加法的定义,被转换为0,就得到了结果。

valueOf()toString()

🔸 valueOf()

valueOf() 方法返回指定对象的原始值。

JavaScript调用valueOf方法将对象转换为原始值。你很少需要自己调用valueOf方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。

🔸 toString()

toString() 方法返回一个表示该对象的字符串。

每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString() 方法被每个 Object 对象继承。

🔸 {}[]valueOf()toString() 的结果是什么?

注意:可以在谷歌浏览器里直接通过括号(),包括值的方式来调用方法

([]).valueOf()
([]).toString()
({}).valueOf()
({}).toString()

在这里插入图片描述

参考

查看原文

查看全部文章

博文系列目录

交流

各系列文章汇总:https://github.com/yuanyuanbyte/Blog

我是圆圆,一名深耕于前端开发的攻城狮。

weixin