各位小伙伴们好,今天咱们来聊一聊JavaScript 中的“类型系统”。函数
可是在开始以前呢咱们能够先思考一个简单的表达式,那就是在 JavaScript 中,“1+‘2’等于多少?”spa
其实这至关因而在问,在 JavaScript 中,让数字和字符串相加是会报错,仍是能够正确执行。翻译
若是能正确执行,那么结果是等于数字 3,仍是字符串“3”,仍是字符串“12”呢?code
若是你尝试用一些其余语言执行数字了字符串相加,会是什么杨的结果呢。对象
好比说用 Python 使用数字和字符串进行相加操做,则会直接返回一个执行错误,错误提示是这样的:ip
>>>1+'2' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str'
可是在 JavaScript 中执行这段表达式,倒是能够返回一个结果的,最终返回的结果是字符串“12”。rem
那么为何一样的表达式,在 Python 和 JavaScript 中执行为何会有不一样的结果?为何在 JavaScript 中执行,输出的是字符串“12”,不是数字 3 或者字符串“3”呢?
在上边的表达式中,涉及到了两种不一样类型的数据的相加。要想理清以上两个问题,咱们就须要知道类型的概念,以及 JavaScript 操做类型的策略。字符串
对机器语言来讲,全部的数据都是一堆二进制代码,CPU 处理这些数据的时候,并无类型的概念,CPU 所作的仅仅是移动数据,好比对其进行移位,相加或相乘。虚拟机
而在高级语言中,咱们都会为操做的数据赋予指定的类型,类型能够确认一个值或者一组值具备特定的意义和目的。因此,类型是高级语言中的概念。it
在 JavaScript 中,你能够这样定义变量:
var num = 100 # 赋值整型变量 let miles = 1000.0 # 浮点型 const name = "John" # 字符串
了解了类型系统,接下来咱们就能够来看看 V8 是怎么处理 1+“2”的了。当有两个值相加的时候,好比:
a+b
V8 会严格根据 ECMAScript 规范来执行操做。ECMAScript 是一个语言标准,JavaScript 就是 ECMAScript 的一个实现,好比在 ECMAScript 就定义了怎么执行加法操做,以下所示:
具体细节你也能够参考规范,我将标准定义的内容翻译以下:
- 把第一个表达式 (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,则:
a. 把 ToString(lprim) 的结果赋给左字符串 (lstr);
b. 把 ToString(rprim) 的结果赋给右字符串 (rstr);
c. 返回左字符串 (lstr) 和右字符串 (rstr) 拼接的字符串。
- 把 ToNumber(lprim) 的结果赋给左数字 (lnum)。
- 把 ToNumber(rprim) 的结果赋给右数字 (rnum)。
- 返回左数字 (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"
你以为执行这段代码会打印出什么内容呢?欢迎你在留言区与我分享讨论。