0
点赞
收藏
分享

微信扫一扫

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?


说明

图解 Google V8 学习笔记。

什么是类型系统 (Type System)?

为什么数字 1 加上字符串 2 输出的结果是字符串 12 ?

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_v8

要搞清上面这个问题,需要知道类型的概念,以及 JavaScript 操作类型的策略。

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

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_类型系统_02

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

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_字符串_03


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

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

​​wiki 百科:类型系统(type system)​​

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

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

V8 会严格根据 ECMAScript 语言标准规范来执行操作。

​​ECMAScript定义加法语义​​

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_字符串_04


翻译如下:

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

里面涉及的 ​​ReturnIfAbrupt​​:

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_赋值_05

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

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

将对象转换为原生类型的流程图:

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_v8_06

两个原生类型相加

  1. 如果其中一个值的类型是字符串时,则另一个值也需要强制转换为字符串,然后做字符串的连接运算。
  2. 在其他情况时,所有的值都会转换为数字类型值,然后做数字的相加。

例子1:

var kaimo = {
toString() {
return "777"
},
valueOf() {
return 666
}
}
kaimo + 1

先使用 ToPrimitive 方法将 kaimo 转换为原生类型,ToPrimitive 会优先调用对象中的 valueOf 方法,返回了 666 Number 类型。

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_字符串_07

例子2:

var kaimo = {
toString() {
return "777"
},
valueOf() {
return 666
}
}
kaimo + "1"

先使用 ToPrimitive 方法将 kaimo 转换为原生类型,ToPrimitive 会优先调用对象中的 valueOf 方法,返回了 666 Number 类型。其中一个值 “1” 的类型是字符串,则另一个值也需要强制转换为字符串,然后做字符串的连接运算。

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_v8_08

例子3:

var kaimo = {
toString() {
return new Object()
},
valueOf() {
return new Object()
}
}
kaimo + 1

因为 ToPrimitive 会先调用 valueOf 方法,发现返回的是一个对象,并不是原生类型,当 ToPrimitive 继续调用 toString 方法时,发现 toString 返回的也是一个对象,都是对象,就无法执行相加运算,这时候虚拟机就会抛出一个异常:​​Uncaught TypeError: Cannot convert object to primitive value​​。

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_字符串_09

ToPrimitive 拓展

图解 Google V8 # 08:类型转换:V8是怎么实现 1 + “2” 的?_v8_10

参考资料

  • ​​https://262.ecma-international.org/6.0/#sec-addition-operator-plus-runtime-semantics-evaluation​​


举报

相关推荐

0 条评论