07类型转换

Review

  1. 2023-02-11 19:09

JavaScript 中的“类型系统”。在理解这个概念之前,先思考一个简单的表达式,那就是在 JavaScript 中,“1+‘2’等于多少?”

>>> 1+'2'
>>> "12"

为什么在 JavaScript 中执行,输出的是字符串“12”,不是数字 3 或者字符串“3”呢?

类型系统 (Type System) #

对机器语言来说,所有的数据都是一堆二进制代码,CPU 处理这些数据的时候,并没有类型的概念,CPU 所做的仅仅是移动数据,比如对其进行移位,相加或相乘。

而在高级语言中,我们都会为操作的数据赋予指定的类型,类型可以确认一个值或者一组值具有特定的意义和目的。所以,类型是高级语言中的概念

在 JavaScript 中,你可以这样定义变量,可以根据数据推断出类型:

var counter = 100 # 赋值整型变量
let miles = 1000.0 # 浮点型
const name = "John" # 字符串

通用的类型有数字类型、字符串、Boolean 类型等等,引入了这些类型之后,编译器或者解释器就可以根据类型来限制一些有害的或者没有意义的操作。

每种语言都定义了自己的类型,还定义了如何操作这些类型,另外还定义了这些类型应该如何相互作用,我们就把这称为类型系统。

在计算机科学中,类型系统(type system)用于定义如何将编程语言中的数值和表达式归类为许多不同的类型,如何操作这些类型,这些类型如何互相作用。

直观地理解,一门语言的类型系统定义了各种类型之间应该如何相互操作,比如,两种不同类型相加应该如何处理,两种相同的类型相加又应该如何处理等。还规定了各种不同类型应该如何相互转换,比如字符串类型如何转换为数字类型。

一个语言的类型系统越强大,那编译器能帮程序员检查的东西就越多,程序员定义“检查规则”的方式就越灵活。

V8 是怎么执行加法操作的? #

V8 会严格根据 ECMAScript 规范来执行操作。ECMAScript 是一个语言标准,JavaScript 就是 ECMAScript 的一个实现,比如在 ECMAScript 就定义了怎么执行加法操作,如下所示:

AdditiveExpression

AdditiveExpression : AdditiveExpression + MultiplicativeExpression

  1. 把第一个表达式 (AdditiveExpression) 的值赋值给左引用 (lref)。
  2. 使用 GetValue(lref) 获取左引用 (lref) 的计算结果,并赋值给左值(lval)。
  3. 使用ReturnIfAbrupt(lval) 如果报错就返回错误。
  4. 把第二个表达式 (MultiplicativeExpression) 的值赋值给右引用 (rref)。
  5. 使用 GetValue(rref) 获取右引用 (rref) 的计算结果,并赋值给 rval
  6. 使用ReturnIfAbrupt(rval) 如果报错就返回错误。
  7. 使用 ToPrimitive(lval) 获取左值 (lval) 的计算结果,并将其赋值给左原生值 (lprim)。
  8. ReturnIfAbrupt(lprim)
  9. 使用 ToPrimitive(rval) 获取右值 (rval) 的计算结果,并将其赋值给右原生值 (rprim)。
  10. ReturnIfAbrupt(rprim)
  11. 如果 Type(lprim) 和 Type(rprim) 中有一个是 String,则:
    • a. 把 ToString(lprim) 的结果赋给左字符串 (lstr);
    • b. 把 ToString(rprim) 的结果赋给右字符串 (rstr);
    • c. 返回左字符串 (lstr) 和右字符串 (rstr) 拼接的字符串。
  12. 把 ToNumber(lprim) 的结果赋给左数字 (lnum)。
  13. 把 ToNumber(rprim) 的结果赋给右数字 (rnum)。
  14. 返回左数字 (lnum) 和右数字 (rnum) 相加的数值。

V8 会提供了一个 ToPrimitive 方法,其作用是将 a 和 b 转换为原始数据类型,转换流程简单理解如下:

  1. 先检测该对象中是否存在 valueOf 方法,如果有并返回了原始类型,那么就使用该值进行强制类型转换;
  2. 如果 valueOf 没有返回原始类型,那么就使用 toString 方法的返回值;
  3. 如果 vauleOf 和 toString 两个方法都不返回基本类型值,便会触发一个 TypeError 的错误。

将对象转换为原生类型的流程图如下所示: ![[a06a2d15cd15_59a72082.webp]]

实际规范是这样的: https://262.ecma-international.org/6.0/#sec-toprimitive ToPrimitive ( input [, PreferredType] )

When the abstract operation OrdinaryToPrimitive is called with arguments O and hint, the following steps are taken:

  1. Assert: Type(O) is Object
  2. Assert: Type(hint) is String and its value is either “string” or “number”.
  3. If hint is “string”, then
    1. Let methodNames be «“toString”, “valueOf”».
  4. Else,
    1. Let methodNames be «“valueOf”, “toString”».
var Obj = {
    toString() {
      return '200'
    }, 
    valueOf() {
      return 100
    }   
}

console.log(Obj+3) // 103
console.log(Obj+'3') // 1003

ToPrimitive的第二个参数期望值,如果没填默认值是number,但是date类型的默认值是string。 Number就是valueOf先调用 string就是toString先调用。Obj.toString()是传递string,toPrimitive(arguments,‘string’)。

所以说,在执行加法操作的时候,V8 会通过 ToPrimitive 方法将对象类型转换为原生类型,最后就是两个原生类型相加,如果其中一个值的类型是字符串时,则另一个值也需要强制转换为字符串,然后做字符串的连接运算。在其他情况时,所有的值都会转换为数字类型值,然后做数字的相加。

在 JavaScript 中,类型系统是依据 ECMAScript 标准来实现的,所以 V8 会严格根据 ECMAScript 标准来执行。在执行加法过程中,V8 会先通过 ToPrimitive 函数,将对象转换为原生的字符串或者是数字类型,在转换过程中,ToPrimitive 会先调用对象的 valueOf 方法,如果没有 valueOf 方法,则调用 toString 方法,如果 vauleOf 和 toString 两个方法都不返回基本类型值,便会触发一个 TypeError 的错误。