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

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

说明

图解 Google V8 学习笔记。



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


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

af7746448e2a4ac9abb1b847b5d4df12.png

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


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

d3fce0d11e8843b99733e9e40352b623.png

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

8431fb7175aa44008119f86bc748fc94.png


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


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


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


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



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


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


ECMAScript定义加法语义

9ddd9e205ec147939d93e245b662da91.png


翻译如下:


   把第一个表达式 (AdditiveExpression) 的值赋值给左引用 (lref)。

   使用 GetValue(lref) 获取左引用 (lref) 的计算结果,并赋值给左值。

   使用 ReturnIfAbrupt(lval) 如果报错就返回错误。

   把第二个表达式 (MultiplicativeExpression) 的值赋值给右引用 (rref)。

   使用 GetValue(rref) 获取右引用 (rref) 的计算结果,并赋值给 rval。

   使用 ReturnIfAbrupt(rval) 如果报错就返回错误。

   使用 ToPrimitive(lval) 获取左值 (lval) 的计算结果,并将其赋值给左原生值 (lprim)。

   使用 ToPrimitive(rval) 获取右值 (rval) 的计算结果,并将其赋值给右原生值 (rprim)。

   如果 Type(lprim) 和 Type(rprim) 中有一个是 String,则:

       把 ToString(lprim) 的结果赋给左字符串 (lstr);

       把 `ToString(rprim) 的结果赋给右字符串 (rstr);

       返回左字符串 (lstr) 和右字符串 (rstr) 拼接的字符串。

   把 `ToNumber(lprim) 的结果赋给左数字 (lnum)。

   把 ToNumber(rprim) 的结果赋给右数字 (rnum)。

   返回左数字 (lnum) 和右数字 (rnum) 相加的数值。


里面涉及的 ReturnIfAbrupt


460ce7d4c1ed4990a99e1fb75d13984b.png


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


   先检测该对象中是否存在 valueOf 方法,如果有并返回了原始类型,那么就使用该值进行强制类型转换;


   如果 valueOf 没有返回原始类型,那么就使用 toString 方法的返回值;


   如果 vauleOf 和 toString 两个方法都不返回基本类型值,便会触发一个 TypeError 的错误。


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

9744c7b0f9954f11bcc98c427867f8c9.png



两个原生类型相加


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



例子1:

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


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


2f02d76bbcb841628820c00778f5e0e1.png


例子2:

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


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


fc9522c1ff8d42fe915c83bdf0afa54e.png

例子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。


bb00e3bdb19b4062985c576f9e413a57.png


ToPrimitive 拓展

aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxOS8xMS8yOC8xNmViMjM0NWFkNzRiMDEx.png



目录
相关文章
|
Web App开发 缓存 JavaScript
图解 Google V8 # 13:字节码(一):V8为什么又重新引入字节码?
图解 Google V8 # 13:字节码(一):V8为什么又重新引入字节码?
236 0
图解 Google V8 # 13:字节码(一):V8为什么又重新引入字节码?
|
缓存 JavaScript 前端开发
图解 Google V8 # 22 :关于内存泄漏、内存膨胀、频繁垃圾回收的解决策略(完结篇)
图解 Google V8 # 22 :关于内存泄漏、内存膨胀、频繁垃圾回收的解决策略(完结篇)
307 0
图解 Google V8 # 22 :关于内存泄漏、内存膨胀、频繁垃圾回收的解决策略(完结篇)
|
Web App开发 JavaScript 前端开发
图解 Google V8 # 21 :垃圾回收(二):V8是如何优化垃圾回收器执行效率的?
图解 Google V8 # 21 :垃圾回收(二):V8是如何优化垃圾回收器执行效率的?
118 0
图解 Google V8 # 21 :垃圾回收(二):V8是如何优化垃圾回收器执行效率的?
|
算法 JavaScript Java
图解 Google V8 # 20 :垃圾回收(一):V8的两个垃圾回收器是如何工作的?
图解 Google V8 # 20 :垃圾回收(一):V8的两个垃圾回收器是如何工作的?
102 0
图解 Google V8 # 20 :垃圾回收(一):V8的两个垃圾回收器是如何工作的?
|
前端开发 JavaScript
图解 Google V8 # 19 :异步编程(二):V8 是如何实现 async/await 的?
图解 Google V8 # 19 :异步编程(二):V8 是如何实现 async/await 的?
132 0
图解 Google V8 # 19 :异步编程(二):V8 是如何实现 async/await 的?
|
消息中间件 前端开发 JavaScript
图解 Google V8 # 18 :异步编程(一):V8是如何实现微任务的?
图解 Google V8 # 18 :异步编程(一):V8是如何实现微任务的?
422 0
图解 Google V8 # 18 :异步编程(一):V8是如何实现微任务的?
|
消息中间件 程序员 Android开发
图解 Google V8 # 17:消息队列:V8是怎么实现回调函数的?
图解 Google V8 # 17:消息队列:V8是怎么实现回调函数的?
109 0
图解 Google V8 # 17:消息队列:V8是怎么实现回调函数的?
|
存储 缓存 索引
图解 Google V8 # 16:V8是怎么通过内联缓存来提升函数执行效率的?
图解 Google V8 # 16:V8是怎么通过内联缓存来提升函数执行效率的?
145 0
图解 Google V8 # 16:V8是怎么通过内联缓存来提升函数执行效率的?
|
JavaScript 前端开发 编译器
图解 Google V8 # 15:隐藏类:如何在内存中快速查找对象属性?
图解 Google V8 # 15:隐藏类:如何在内存中快速查找对象属性?
140 0
图解 Google V8 # 15:隐藏类:如何在内存中快速查找对象属性?
|
JavaScript 前端开发 Java
图解 Google V8 # 14:字节码(二):解释器是如何解释执行字节码的?
图解 Google V8 # 14:字节码(二):解释器是如何解释执行字节码的?
242 0
图解 Google V8 # 14:字节码(二):解释器是如何解释执行字节码的?