各位小伙伴们好,明天咱们来聊一聊 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 就定义了怎么执行加法操作,如下所示:
具体细节你也能够参考标准,我将规范定义的内容翻译如下:
- 把第一个表达式 (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"
你感觉执行这段代码会打印出什么内容呢?欢送你在留言区与我分享探讨。