各位小夥伴們好,今天咱們來聊一聊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"
你以爲執行這段代碼會打印出什麼內容呢?歡迎你在留言區與我分享討論。