前言
在 Day01 裡面,我們嘗試了許多種變數相關的指令,再來我們要試試看各種判斷式,來看 V8 會把這些判斷式翻譯成什麼形式。
簡單的 if
先來一個最簡單的 if 試試看:
function find_me_test() {
if (15 > 10) {
console.log(true)
} else {
console.log(false)
}
}
find_me_test()
結果為:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
21 E> 0x2c3d05a9dc9a @ 0 : a5 StackCheck
28 S> 0x2c3d05a9dc9b @ 1 : 0c 0f LdaSmi [15]
0x2c3d05a9dc9d @ 3 : 26 fb Star r0
0x2c3d05a9dc9f @ 5 : 0c 0a LdaSmi [10]
35 E> 0x2c3d05a9dca1 @ 7 : 6a fb 00 TestGreaterThan r0, [0]
0x2c3d05a9dca4 @ 10 : 99 17 JumpIfFalse [23] (0x2c3d05a9dcbb @ 33)
47 S> 0x2c3d05a9dca6 @ 12 : 13 00 01 LdaGlobal [0], [1]
0x2c3d05a9dca9 @ 15 : 26 fa Star r1
55 E> 0x2c3d05a9dcab @ 17 : 28 fa 01 03 LdaNamedProperty r1, [1], [3]
0x2c3d05a9dcaf @ 21 : 26 fb Star r0
0x2c3d05a9dcb1 @ 23 : 10 LdaTrue
0x2c3d05a9dcb2 @ 24 : 26 f9 Star r2
55 E> 0x2c3d05a9dcb4 @ 26 : 59 fb fa f9 05 CallProperty1 r0, r1, r2, [5]
0x2c3d05a9dcb9 @ 31 : 8b 15 Jump [21] (0x2c3d05a9dcce @ 52)
80 S> 0x2c3d05a9dcbb @ 33 : 13 00 01 LdaGlobal [0], [1]
0x2c3d05a9dcbe @ 36 : 26 fa Star r1
88 E> 0x2c3d05a9dcc0 @ 38 : 28 fa 01 03 LdaNamedProperty r1, [1], [3]
0x2c3d05a9dcc4 @ 42 : 26 fb Star r0
0x2c3d05a9dcc6 @ 44 : 11 LdaFalse
0x2c3d05a9dcc7 @ 45 : 26 f9 Star r2
88 E> 0x2c3d05a9dcc9 @ 47 : 59 fb fa f9 07 CallProperty1 r0, r1, r2, [7]
0x2c3d05a9dcce @ 52 : 0d LdaUndefined
103 S> 0x2c3d05a9dccf @ 53 : a9 Return
Constant pool (size = 2)
0x2c3d05a9dc19: [FixedArray] in OldSpace
- map: 0x2c3d196007b1 <Map>
- length: 2
0: 0x2c3dd72100e9 <String[#7]: console>
1: 0x2c3dd720fbe9 <String[#3]: log>
Handler Table (size = 0)
true
一樣為了方便起見,讓我把程式碼簡化一下:
21 E> 0x2c3d05a9dc9a @ 0 : a5 StackCheck
28 S> 0x2c3d05a9dc9b @ 1 : 0c 0f LdaSmi [15]
0x2c3d05a9dc9d @ 3 : 26 fb Star r0
0x2c3d05a9dc9f @ 5 : 0c 0a LdaSmi [10]
35 E> 0x2c3d05a9dca1 @ 7 : 6a fb 00 TestGreaterThan r0, [0]
0x2c3d05a9dca4 @ 10 : 99 17 JumpIfFalse [23] (0x2c3d05a9dcbb @ 33)
47 S> 0x2c3d05a9dca6 @ 12 : 13 00 01 console.log(true)
0x2c3d05a9dcb9 @ 31 : 8b 15 Jump [21] (0x2c3d05a9dcce @ 52)
80 S> 0x2c3d05a9dcbb @ 33 : 13 00 01 console.log(false)
0x2c3d05a9dcce @ 52 : 0d LdaUndefined
103 S> 0x2c3d05a9dccf @ 53 : a9 Return
前面載入值的部分應該很熟悉,就不用多說了。判斷主要是在這一行:TestGreaterThan r0, [0]
,這一行指令就是:acc = acc > r0
,把判斷後的結果存回 acc 當中。
而下一行 JumpIfFalse [23] (0x2c3d05a9dcbb @ 33)
可以直接看原始碼裡面的註釋:
// JumpIfFalse <imm>
//
// Jump by the number of bytes represented by an immediate operand if the
// accumulator contains false. This only works for boolean inputs, and
// will misbehave if passed arbitrary input values.
如果 acc 裡面的值是 false,就往後跳 <imm>
個 bytes,這邊接的參數是 23,所以會跳到 10+23 = 33
的地方,而 V8 已經貼心地在後面標明了跳轉的目的地:(0x2c3d05a9dcbb @ 33)
。
以我們的 case 來說,結果會是 true,所以不會跳轉,會繼續往下執行console.log(true)
,然後進行無條件跳轉:Jump [21] (0x2c3d05a9dcce @ 52)
,跳到函式結束的地方。
如果有寫過組合語言的話,對於這種形式一定不陌生,這就是經典的 if else 結構。
更簡單的 if
接著我們來試一個更簡單的 case:
function find_me_test() {
if (true) {
console.log(true)
} else {
console.log(false)
}
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
21 E> 0x34c38d21dc92 @ 0 : a5 StackCheck
44 S> 0x34c38d21dc93 @ 1 : 13 00 00 LdaGlobal [0], [0]
0x34c38d21dc96 @ 4 : 26 fa Star r1
52 E> 0x34c38d21dc98 @ 6 : 28 fa 01 02 LdaNamedProperty r1, [1], [2]
0x34c38d21dc9c @ 10 : 26 fb Star r0
0x34c38d21dc9e @ 12 : 10 LdaTrue
0x34c38d21dc9f @ 13 : 26 f9 Star r2
52 E> 0x34c38d21dca1 @ 15 : 59 fb fa f9 04 CallProperty1 r0, r1, r2, [4]
0x34c38d21dca6 @ 20 : 0d LdaUndefined
100 S> 0x34c38d21dca7 @ 21 : a9 Return
Constant pool (size = 2)
0x34c38d21dc19: [FixedArray] in OldSpace
- map: 0x34c33bf807b1 <Map>
- length: 2
0: 0x34c38cf100e9 <String[#7]: console>
1: 0x34c38cf0fbe9 <String[#3]: log>
Handler Table (size = 0)
true
很明顯地可以看到 V8 Ignition 在這邊做了優化,因為條件是 true,所以其實根本不需要 if else,因此可以從 bytecode 中看到根本沒有 if else,只留下了 if(true)
後面的那一段程式碼。
而我們一開始的範例 if (15 > 10)
其實也會永遠都是 true,只是 Ignition 沒有在這部分做優化而已(但我猜測 TurboFan 有)。
多個 else if
接著來試試看多個 if else 的情況:
function find_me_test() {
var a = 'hello'
if (a === 'yo') {
console.log('yo')
} else if (a === 'hey') {
console.log('hey')
} else if (a === 'hello') {
console.log('hello')
} else {
console.log('default')
}
}
find_me_test()
我一樣先把 bytecode 稍微處理一下:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 32
21 E> 0x27b29309dd32 @ 0 : a5 StackCheck
36 S> 0x27b29309dd33 @ 1 : 12 00 LdaConstant [0]
0x27b29309dd35 @ 3 : 26 fb Star r0
46 S> 0x27b29309dd37 @ 5 : 12 01 LdaConstant [1]
52 E> 0x27b29309dd39 @ 7 : 68 fb 00 TestEqualStrict r0, [0]
0x27b29309dd3c @ 10 : 99 18 JumpIfFalse [24] (0x27b29309dd54 @ 34)
68 S> 0x27b29309dd3e @ 12 : 13 02 01 console.log('yo')
0x27b29309dd52 @ 32 : 8b 50 Jump [80] (0x27b29309dda2 @ 112)
95 S> 0x27b29309dd54 @ 34 : 12 04 LdaConstant [4]
101 E> 0x27b29309dd56 @ 36 : 68 fb 07 TestEqualStrict r0, [7]
0x27b29309dd59 @ 39 : 99 18 JumpIfFalse [24] (0x27b29309dd71 @ 63)
118 S> 0x27b29309dd5b @ 41 : 13 02 01 console.log('hey')
0x27b29309dd6f @ 61 : 8b 33 Jump [51] (0x27b29309dda2 @ 112)
146 S> 0x27b29309dd71 @ 63 : 12 00 LdaConstant [0]
152 E> 0x27b29309dd73 @ 65 : 68 fb 0a TestEqualStrict r0, [10]
0x27b29309dd76 @ 68 : 99 18 JumpIfFalse [24] (0x27b29309dd8e @ 92)
171 S> 0x27b29309dd78 @ 70 : 13 02 01 console.log('hello')
0x27b29309dd8c @ 90 : 8b 16 Jump [22] (0x27b29309dda2 @ 112)
207 S> 0x27b29309dd8e @ 92 : 13 02 01 console.log('default')
0x27b29309dda2 @ 112 : 0d LdaUndefined
234 S> 0x27b29309dda3 @ 113 : a9 Return
Constant pool (size = 6)
0x27b29309dc79: [FixedArray] in OldSpace
- map: 0x27b28a1007b1 <Map>
- length: 6
0: 0x27b29309dbd1 <String[#5]: hello>
1: 0x27b29309dbe9 <String[#2]: yo>
2: 0x27b2f3f900e9 <String[#7]: console>
3: 0x27b2f3f8fbe9 <String[#3]: log>
4: 0x27b29309dc01 <String[#3]: hey>
5: 0x27b28a1038e1 <String[#7]: default>
Handler Table (size = 0)
hello
這個結構也相對簡單,比較的形式都是一樣的:
LdaConstant [1]
TestEqualStrict r0, [0]
JumpIfFalse [24] (0x27b29309dd54 @ 34) // 跳到下一個判斷
console.log('yo')
Jump [80] (0x27b29309dda2 @ 112) // 跳到結尾
先跟某個字串做比較,若是成功的話就執行程式碼,然後跳到結尾。比對失敗的話就跳到下一個判斷。
在看這一段 bytecode 的時候,其實會發現 V8 共用了 constant pool 裡面的東西,例如說 if (a === 'yo')
跟 console.log('yo')
的那個 yo
都是指向 constant pool 中的同一個物件。
於是我想來做一個小實驗看看:
function find_me_test() {
var a = 'hello'
var b = 'hello'
var c = 'hello'
if (c === 'hello') {
console.log('hello')
}
}
find_me_test()
得到的結果為:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 48
21 E> 0x1823f781dd02 @ 0 : a5 StackCheck
36 S> 0x1823f781dd03 @ 1 : 12 00 LdaConstant [0]
0x1823f781dd05 @ 3 : 26 fb Star r0
54 S> 0x1823f781dd07 @ 5 : 12 00 LdaConstant [0]
0x1823f781dd09 @ 7 : 26 fa Star r1
72 S> 0x1823f781dd0b @ 9 : 12 00 LdaConstant [0]
0x1823f781dd0d @ 11 : 26 f9 Star r2
82 S> 0x1823f781dd0f @ 13 : 12 00 LdaConstant [0]
88 E> 0x1823f781dd11 @ 15 : 68 f9 00 TestEqualStrict r2, [0]
0x1823f781dd14 @ 18 : 99 16 JumpIfFalse [22] (0x1823f781dd2a @ 40)
107 S> 0x1823f781dd16 @ 20 : 13 01 01 LdaGlobal [1], [1]
0x1823f781dd19 @ 23 : 26 f7 Star r4
115 E> 0x1823f781dd1b @ 25 : 28 f7 02 03 LdaNamedProperty r4, [2], [3]
0x1823f781dd1f @ 29 : 26 f8 Star r3
0x1823f781dd21 @ 31 : 12 00 LdaConstant [0]
0x1823f781dd23 @ 33 : 26 f6 Star r5
115 E> 0x1823f781dd25 @ 35 : 59 f8 f7 f6 05 CallProperty1 r3, r4, r5, [5]
0x1823f781dd2a @ 40 : 0d LdaUndefined
132 S> 0x1823f781dd2b @ 41 : a9 Return
Constant pool (size = 3)
0x1823f781dc79: [FixedArray] in OldSpace
- map: 0x18239b2007b1 <Map>
- length: 3
0: 0x1823f781dc01 <String[#5]: hello>
1: 0x1823c6b900e9 <String[#7]: console>
2: 0x1823c6b8fbe9 <String[#3]: log>
Handler Table (size = 0)
hello
事實證明,裡面所有出現的 hello
都是指向同一個地方,因此儘管程式碼裡面有幾千個 hello,constant pool 裡面永遠都只會有一個,無論你用了一個 hello 還是一百個 hello,它們佔用的空間是一樣的。
switch case
接著來看一下 switch case,為了方便對照,這邊的邏輯跟上面的 if else 的範例是一模一樣的:
function find_me_test() {
var a = 'hello'
switch(a) {
case 'yo':
console.log('yo')
break;
case 'hey':
console.log('hey')
break
case 'hello':
console.log('hello')
break
default:
console.log('default')
}
}
find_me_test()
一樣把 bytecode 先做處理,比較好看:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 40
21 E> 0x37a30dc9dd2a @ 0 : a5 StackCheck
36 S> 0x37a30dc9dd2b @ 1 : 12 00 LdaConstant [0]
0x37a30dc9dd2d @ 3 : 26 fb Star r0
46 S> 0x37a30dc9dd2f @ 5 : 12 01 LdaConstant [1]
0x37a30dc9dd31 @ 7 : 68 fb 00 TestEqualStrict r0, [0]
0x37a30dc9dd34 @ 10 : 27 fb fa Mov r0, r1
0x37a30dc9dd37 @ 13 : 98 12 JumpIfTrue [18] (0x37a30dc9dd49 @ 31)
0x37a30dc9dd39 @ 15 : 12 02 LdaConstant [2]
0x37a30dc9dd3b @ 17 : 68 fa 00 TestEqualStrict r1, [0]
0x37a30dc9dd3e @ 20 : 98 21 JumpIfTrue [33] (0x37a30dc9dd5f @ 53)
0x37a30dc9dd40 @ 22 : 12 00 LdaConstant [0]
0x37a30dc9dd42 @ 24 : 68 fa 00 TestEqualStrict r1, [0]
0x37a30dc9dd45 @ 27 : 98 30 JumpIfTrue [48] (0x37a30dc9dd75 @ 75)
0x37a30dc9dd47 @ 29 : 8b 44 Jump [68] (0x37a30dc9dd8b @ 97)
79 S> 0x37a30dc9dd49 @ 31 : 13 03 01 console.log('yo')
103 S> 0x37a30dc9dd5d @ 51 : 8b 42 Jump [66] (0x37a30dc9dd9f @ 117)
133 S> 0x37a30dc9dd5f @ 53 : 13 03 01 console.log('hey')
158 S> 0x37a30dc9dd73 @ 73 : 8b 2c Jump [44] (0x37a30dc9dd9f @ 117)
188 S> 0x37a30dc9dd75 @ 75 : 13 03 01 console.log('hello')
215 S> 0x37a30dc9dd89 @ 95 : 8b 16 Jump [22] (0x37a30dc9dd9f @ 117)
240 S> 0x37a30dc9dd8b @ 97 : 13 03 01 console.log('default')
0x37a30dc9dd9f @ 117 : 0d LdaUndefined
267 S> 0x37a30dc9dda0 @ 118 : a9 Return
Constant pool (size = 6)
0x37a30dc9dc79: [FixedArray] in OldSpace
- map: 0x37a3e6e807b1 <Map>
- length: 6
0: 0x37a30dc9dbd1 <String[#5]: hello>
1: 0x37a30dc9dbe9 <String[#2]: yo>
2: 0x37a30dc9dc01 <String[#3]: hey>
3: 0x37a3de5900e9 <String[#7]: console>
4: 0x37a3de58fbe9 <String[#3]: log>
5: 0x37a3e6e838e1 <String[#7]: default>
Handler Table (size = 0)
hello
可以看到 switch case 的結構與 if else 不太一樣,switch case 的結構是:
if (a === 'yo') goto yo
if (a === 'hey') goto hey
if (a === 'hello') goto hello
goto default
yo:
console.log('yo')
goto return
hey:
console.log('hey')
goto return
hello:
console.log('hello')
goto return
default:
console.log('default')
return:
return undefined
先把判斷式全部都放在一起,如果符合的話就跳到執行的區塊去,執行完以後結束;不符合的話就看下一個判斷式。所以判斷式全部在一起,然後需要執行的程式碼也全部都放一起。
而 if else 的結構是這樣的:
if (a !== 'yo') goto next if
console.log('yo')
goto return
if (a !== 'hey') goto next if
console.log('hey')
goto return
if (a !== 'hello') goto default
console.log('hello')
goto return
default:
console.log('default')
return:
return undefined
它把要執行的程式碼跟判斷式放在一塊,如果不符合條件就跳到下一個 if 去。所以很奇妙地,switch case 與 if else 的 bytecode 邏輯是相反的,一個是:「如果...為 true」,一個是:「如果...為 false」。
那為什麼會有這種差異呢?我懷疑跟 switch case 的特性:break
有關,因此把其中一個 break 拿掉來測試,順便把條件換了一下:
function find_me_test() {
var a = 'yo'
switch(a) {
case 'yo':
console.log('yo')
case 'hey':
console.log('hey')
break
case 'hello':
console.log('hello')
break
default:
console.log('default')
}
}
find_me_test()
先來講講如果忘記放 break 會發生什麼事情,如果忘記放的話,程式碼就會繼續執行,而且「不管下一個 case 的條件符合與否,都會執行,直到碰到 break 為止」
因此上面的程式碼跑進去 case 'yo'
那一段之後,會繼續往下跑到 case 'hello'
那一段,印出 hello 之後才結束。
產生出來的 bytecode 如下:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 40
21 E> 0x38c820f9dd2a @ 0 : a5 StackCheck
36 S> 0x38c820f9dd2b @ 1 : 12 00 LdaConstant [0]
0x38c820f9dd2d @ 3 : 26 fb Star r0
43 S> 0x38c820f9dd2f @ 5 : 12 00 LdaConstant [0]
0x38c820f9dd31 @ 7 : 68 fb 00 TestEqualStrict r0, [0]
0x38c820f9dd34 @ 10 : 27 fb fa Mov r0, r1
0x38c820f9dd37 @ 13 : 98 12 JumpIfTrue [18] (0x38c820f9dd49 @ 31)
0x38c820f9dd39 @ 15 : 12 01 LdaConstant [1]
0x38c820f9dd3b @ 17 : 68 fa 00 TestEqualStrict r1, [0]
0x38c820f9dd3e @ 20 : 98 1f JumpIfTrue [31] (0x38c820f9dd5d @ 51)
0x38c820f9dd40 @ 22 : 12 02 LdaConstant [2]
0x38c820f9dd42 @ 24 : 68 fa 00 TestEqualStrict r1, [0]
0x38c820f9dd45 @ 27 : 98 2e JumpIfTrue [46] (0x38c820f9dd73 @ 73)
0x38c820f9dd47 @ 29 : 8b 42 Jump [66] (0x38c820f9dd89 @ 95)
76 S> 0x38c820f9dd49 @ 31 : 13 03 01 console.log('yo')
117 S> 0x38c820f9dd5d @ 51 : 13 03 01 console.log('hey')
142 S> 0x38c820f9dd71 @ 71 : 8b 2c Jump [44] (0x38c820f9dd9d @ 115)
172 S> 0x38c820f9dd73 @ 73 : 13 03 01 console.log('hello')
199 S> 0x38c820f9dd87 @ 93 : 8b 16 Jump [22] (0x38c820f9dd9d @ 115)
224 S> 0x38c820f9dd89 @ 95 : 13 03 01 consle.log('default')
0x38c820f9dd9d @ 115 : 0d LdaUndefined
251 S> 0x38c820f9dd9e @ 116 : a9 Return
Constant pool (size = 6)
0x38c820f9dc79: [FixedArray] in OldSpace
- map: 0x38c840f007b1 <Map>
- length: 6
0: 0x38c820f9dbd1 <String[#2]: yo>
1: 0x38c820f9dbe9 <String[#3]: hey>
2: 0x38c820f9dc01 <String[#5]: hello>
3: 0x38c8c27900e9 <String[#7]: console>
4: 0x38c8c278fbe9 <String[#3]: log>
5: 0x38c840f038e1 <String[#7]: default>
Handler Table (size = 0)
yo
hey
跟一般正常的 switch case 比起來,差在哪邊?差在 console.log('yo')
跟 console.log('hey')
之間沒有 jump,所以兩個段落都會被執行到。所以 break 在這邊的作用就是在每一個段落間都加上一個 jump,避免重複執行。
或是換句話說,就是上面我們提到的同一個結構,只是把 yo
那一段的 goto return
拿掉。
if (a === 'yo') goto yo
if (a === 'hey') goto hey
if (a === 'hello') goto hello
goto default
yo:
console.log('yo')
goto return // 這行拿掉
hey:
console.log('hey')
goto return
hello:
console.log('hello')
goto return
default:
console.log('default')
return:
return undefined
看到這個特性,就不難理解為什麼 if else 與 switch case 的結構長得不一樣了。因為如果是採用跟 if else 相同的結構,很難達成這一個功能,沒辦法在進入某一個條件之後,無條件去執行下一個段落的程式碼。
除此之外,switch case 還有一個奇妙的地方是:Mov r0, r1
,會把 r0 的值搬到 r1 去,但其實光從 bytecode,我看不出來為什麼要這樣做,因為把值留在 r0 也是完全 ok 的。
三元運算子
最後,我們來試試看三元運算子:
function find_me_test() {
var a = 2 > 1 ? 'first' : 'second'
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 16
21 E> 0x3db96639dca2 @ 0 : a5 StackCheck
36 S> 0x3db96639dca3 @ 1 : 0c 02 LdaSmi [2]
0x3db96639dca5 @ 3 : 26 fa Star r1
0x3db96639dca7 @ 5 : 0c 01 LdaSmi [1]
38 E> 0x3db96639dca9 @ 7 : 6a fa 00 TestGreaterThan r1, [0]
0x3db96639dcac @ 10 : 99 06 JumpIfFalse [6] (0x3db96639dcb2 @ 16)
0x3db96639dcae @ 12 : 12 00 LdaConstant [0]
0x3db96639dcb0 @ 14 : 8b 04 Jump [4] (0x3db96639dcb4 @ 18)
0x3db96639dcb2 @ 16 : 12 01 LdaConstant [1]
0x3db96639dcb4 @ 18 : 26 fb Star r0
0x3db96639dcb6 @ 20 : 0d LdaUndefined
63 S> 0x3db96639dcb7 @ 21 : a9 Return
Constant pool (size = 2)
0x3db96639dc31: [FixedArray] in OldSpace
- map: 0x3db95d5007b1 <Map>
- length: 2
0: 0x3db9e389da79 <String[#5]: first>
1: 0x3db95d503239 <String[#6]: second>
Handler Table (size = 0)
乍看之下其實跟 if else 的結構很類似,但其實有一個很大的差別。我們先來看一下 if else 版本會長怎樣:
function find_me_test() {
if (2 > 1) {
var a = 'first'
} else {
var a = 'second'
}
}
find_me_test()
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 16
21 E> 0x37542301dcaa @ 0 : a5 StackCheck
28 S> 0x37542301dcab @ 1 : 0c 02 LdaSmi [2]
0x37542301dcad @ 3 : 26 fa Star r1
0x37542301dcaf @ 5 : 0c 01 LdaSmi [1]
34 E> 0x37542301dcb1 @ 7 : 6a fa 00 TestGreaterThan r1, [0]
0x37542301dcb4 @ 10 : 99 08 JumpIfFalse [8] (0x37542301dcbc @ 18)
53 S> 0x37542301dcb6 @ 12 : 12 00 LdaConstant [0]
0x37542301dcb8 @ 14 : 26 fb Star r0
0x37542301dcba @ 16 : 8b 06 Jump [6] (0x37542301dcc0 @ 22)
84 S> 0x37542301dcbc @ 18 : 12 01 LdaConstant [1]
0x37542301dcbe @ 20 : 26 fb Star r0
0x37542301dcc0 @ 22 : 0d LdaUndefined
97 S> 0x37542301dcc1 @ 23 : a9 Return
Constant pool (size = 2)
0x37542301dc31: [FixedArray] in OldSpace
- map: 0x3754e7e807b1 <Map>
- length: 2
0: 0x37549ac9da79 <String[#5]: first>
1: 0x3754e7e83239 <String[#6]: second>
Handler Table (size = 0)
大家有看出差別嗎?if else 的本質還是兩個不同的程式碼區塊,所以兩塊都會有兩個動作:LdaConstant
與 Star r0
。
而三元運算子已經預設是要賦值了,所以在 bytecode 裡面的分支只決定了要載入哪一個值到 acc 裡面去,決定以後再統一 Star r0
,因此從頭到尾只會有一個 Star r0
。
那三元運算子是否也會像 if 那樣做優化呢?
function find_me_test() {
var a = true ? 'first' : 'second'
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 8
21 E> 0x2118d919dc9a @ 0 : a5 StackCheck
36 S> 0x2118d919dc9b @ 1 : 12 00 LdaConstant [0]
0x2118d919dc9d @ 3 : 26 fb Star r0
0x2118d919dc9f @ 5 : 0d LdaUndefined
62 S> 0x2118d919dca0 @ 6 : a9 Return
Constant pool (size = 1)
0x2118d919dc31: [FixedArray] in OldSpace
- map: 0x211828d007b1 <Map>
- length: 1
0: 0x21186809da79 <String[#5]: first>
Handler Table (size = 0)
答案是會的。對於已經知道的結果,便不會再產生分支,而是直接賦值。
結語
在這一篇文章中我們觀察了各種判斷式的特性,得到了一些有趣的結論:
- 對於
if (true)
這種 case,V8 Ignition 會做優化,if (2 > 1)
則不會 - if else 的結構與 switch case 不一樣,後者的結構猜測是為了 break 而定
- 三元運算子的本質跟 if else 差不多
- 在程式碼中同一個字串是可以被重複利用的,都是指向同一個 constant pool 的元素