乐趣区

关于javascript:了解V8二类型转换V8是怎么实现12的

各位小伙伴们好,明天咱们来聊一聊 JavaScript 中的“类型零碎”。

然而在开始之前呢咱们能够先思考一个简略的表达式,那就是在 JavaScript 中,“1+‘2’等于多少?”

其实这相当于是在问,在 JavaScript 中,让 数字 字符串 相加是会报错,还是能够正确执行。

如果能正确执行,那么后果是等于数字 3,还是字符串“3”,还是字符串“12”呢?

如果你尝试用一些其余语言执行数字了字符串相加,会是什么杨的后果呢。

比如说用 Python 应用数字和字符串进行相加操作,则会间接返回一个执行谬误,谬误提醒是这样的:

  >>>1+'2'
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: unsupported operand type(s) for +: 'int' and 
  'str'

然而在 JavaScript 中执行这段表达式,却是能够返回一个后果的,最终返回的后果是字符串“12”。

那么为什么同样的表达式,在 Python 和 JavaScript 中执行为什么会有不同的后果?为什么在 JavaScript 中执行,输入的是字符串“12”,不是数字 3 或者字符串“3”呢?

什么是类型零碎 (Type System)?

在上边的表达式中,波及到了两种不同类型的数据的相加。要想理清以上两个问题,咱们就须要晓得类型的概念,以及 JavaScript 操作类型的策略。

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

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

在 JavaScript 中,你能够这样定义变量:


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

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

理解了类型零碎,接下来咱们就能够来看看 V8 是怎么解决 1+“2”的了。当有两个值相加的时候,比方:


  a+b

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


具体细节你也能够参考标准,我将规范定义的内容翻译如下:

  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,则:

    a. 把 ToString(lprim) 的后果赋给左字符串 (lstr);

    b. 把 ToString(rprim) 的后果赋给右字符串 (rstr);

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

  10. 把 ToNumber(lprim) 的后果赋给左数字 (lnum)。
  11. 把 ToNumber(rprim) 的后果赋给右数字 (rnum)。
  12. 返回左数字 (lnum) 和右数字 (rnum) 相加的数值。

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

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

当 V8 执行 1+“2”时,因为这是两个原始值 相加 ,原始值相加的时候,如果其中一项是 字符串,那么 V8 会默认将另外一个值也转换为字符串,相当于执行了上面的操作:Number(1).toString() + “2”

这里,把数字 1 偷偷转换为字符串“1”的过程也称为强制类型转换,因为这种转换是隐式的,所以如果咱们不相熟语义,那么就很容易判断谬误。

咱们还能够再看一个例子来验证下面流程,你能够看上面的代码:


  var Obj = {toString() {return '200'}, 
      valueOf() {return 100}   
    }
    Obj+3

执行这段代码,你感觉应该返回什么内容呢?

下面咱们介绍过了,因为须要先应用 ToPrimitive 办法将 Obj 转换为原生类型,而 ToPrimitive 会优先调用对象中的 valueOf 办法,因为 valueOf 返回了 100,那么 Obj 就会被转换为数字 100,那么数字 100 加数字 3,那么后果当然是 103 了。

如果我革新下代码,让 valueOf 办法和 toString 办法都返回对象,其革新后的代码如下:


  var Obj = {toString() {return new Object()
      }, 
      valueOf() {return new Object()
      }   
  }
  Obj+3

再执行这段代码,你感觉应该返回什么内容呢?

因为 ToPrimitive 会先调用 valueOf 办法,发现返回的是一个对象,并不是原生类型,当 ToPrimitive 持续调用 toString 办法时,发现 toString 返回的也是一个对象,都是对象,就无奈执行相加运算了,这时候虚拟机就会抛出一个异样,异样如下所示:


  VM263:9 Uncaught TypeError: Cannot convert object to primitive value
    at <anonymous>:9:6

提醒的是类型谬误,谬误起因是无奈将对象类型转换为原生类型。

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

总结

明天咱们次要理解了 JavaScript 中的类型零碎是怎么工作的。类型零碎定义了语言该当如何操作类型,以及这些类型如何相互作用。

在 JavaScript 中,数字和字符串相加会返回一个新的字符串,这是因为 JavaScript 认为字符串和数字相加是有意义的,V8 会将其中的数字转换为字符,而后执行两个字符串的相加操作,最终失去的是一个新的字符串。

在 JavaScript 中,类型零碎是根据 ECMAScript 规范来实现的,所以 V8 会严格依据 ECMAScript 规范来执行。

在执行加法过程中,V8 会先通过 ToPrimitive 函数,将对象转换为原生的字符串或者是数字类型,在转换过程中,ToPrimitive 会先调用对象的 valueOf 办法,如果没有 valueOf 办法,则调用 toString 办法,如果 vauleOf 和 toString 两个办法都不返回根本类型值,便会触发一个 TypeError 的谬误。

思考题

咱们一起来剖析一段代码:


  var Obj = {toString() {return "200"}, 
    valueOf() {return 100}   
  }
  Obj+"3"

你感觉执行这段代码会打印出什么内容呢?欢送你在留言区与我分享探讨。

退出移动版