前言
終於要進入比較有趣的主題啦!這一篇的內容會從各個角度去切入函式這個主題,一起來看看會被翻譯成怎樣的 bytecode。
一般函式
先從最簡單的開始:
function find_me_test() {
var ret = find_me_add(1, 2)
}
function find_me_add(a, b) {
return a+b
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 32
21 E> 0x2b1fe039ddf2 @ 0 : a5 StackCheck
38 S> 0x2b1fe039ddf3 @ 1 : 13 00 00 LdaGlobal [0], [0]
0x2b1fe039ddf6 @ 4 : 26 fa Star r1
0x2b1fe039ddf8 @ 6 : 0c 01 LdaSmi [1]
0x2b1fe039ddfa @ 8 : 26 f9 Star r2
0x2b1fe039ddfc @ 10 : 0c 02 LdaSmi [2]
0x2b1fe039ddfe @ 12 : 26 f8 Star r3
38 E> 0x2b1fe039de00 @ 14 : 5e fa f9 f8 02 CallUndefinedReceiver2 r1, r2, r3, [2]
0x2b1fe039de05 @ 19 : 26 fb Star r0
0x2b1fe039de07 @ 21 : 0d LdaUndefined
56 S> 0x2b1fe039de08 @ 22 : a9 Return
Constant pool (size = 1)
0x2b1fe039dd89: [FixedArray] in OldSpace
- map: 0x2b1f074807b1 <Map>
- length: 1
0: 0x2b1fe039d901 <String[#11]: find_me_add>
Handler Table (size = 0)
[generated bytecode for function: find_me_add]
Parameter count 3
Frame size 0
79 E> 0x2b1fe039df22 @ 0 : a5 StackCheck
90 S> 0x2b1fe039df23 @ 1 : 25 02 Ldar a1
98 E> 0x2b1fe039df25 @ 3 : 34 03 00 Add a0, [0]
100 S> 0x2b1fe039df28 @ 6 : a9 Return
Constant pool (size = 0)
Handler Table (size = 0)
有個比較有趣的地方是find_me_add
是從 global 載入進來的,而在呼叫的時候用了CallUndefinedReceiver2
,後面的 2 就是有兩個參數的意思。而在 find_me_add
裡面 a0 跟 a1 分別代表第一個跟第二個參數,相加以後回傳。
接著來試試看 function expression:
var find_me_add = function(a, b) {
return a + b
}
function find_me_test() {
var ret = find_me_add(1, 2)
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 32
74 E> 0x215cbd21dde2 @ 0 : a5 StackCheck
91 S> 0x215cbd21dde3 @ 1 : 13 00 00 LdaGlobal [0], [0]
0x215cbd21dde6 @ 4 : 26 fa Star r1
0x215cbd21dde8 @ 6 : 0c 01 LdaSmi [1]
0x215cbd21ddea @ 8 : 26 f9 Star r2
0x215cbd21ddec @ 10 : 0c 02 LdaSmi [2]
0x215cbd21ddee @ 12 : 26 f8 Star r3
91 E> 0x215cbd21ddf0 @ 14 : 5e fa f9 f8 02 CallUndefinedReceiver2 r1, r2, r3, [2]
0x215cbd21ddf5 @ 19 : 26 fb Star r0
0x215cbd21ddf7 @ 21 : 0d LdaUndefined
109 S> 0x215cbd21ddf8 @ 22 : a9 Return
Constant pool (size = 1)
0x215cbd21dd71: [FixedArray] in OldSpace
- map: 0x215c731007b1 <Map>
- length: 1
0: 0x215cbd21d8c9 <String[#11]: find_me_add>
Handler Table (size = 0)
[generated bytecode for function: find_me_add]
Parameter count 3
Frame size 0
26 E> 0x215cbd21df0a @ 0 : a5 StackCheck
37 S> 0x215cbd21df0b @ 1 : 25 02 Ldar a1
46 E> 0x215cbd21df0d @ 3 : 34 03 00 Add a0, [0]
49 S> 0x215cbd21df10 @ 6 : a9 Return
Constant pool (size = 0)
Handler Table (size = 0)
跟之前的版本一模一樣,不過在最外層的程式碼應該要有差才對,我們試試看把 function 宣告在裡面:
function find_me_test() {
function find_me_add(a, b) {
return a + b
}
var ret = find_me_add(1, 2)
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 40
0x2a0c4bb9de02 @ 0 : 81 00 00 02 CreateClosure [0], [0], #2
0x2a0c4bb9de06 @ 4 : 26 fb Star r0
21 E> 0x2a0c4bb9de08 @ 6 : a5 StackCheck
90 S> 0x2a0c4bb9de09 @ 7 : 0c 01 LdaSmi [1]
0x2a0c4bb9de0b @ 9 : 26 f8 Star r3
0x2a0c4bb9de0d @ 11 : 0c 02 LdaSmi [2]
0x2a0c4bb9de0f @ 13 : 26 f7 Star r4
90 E> 0x2a0c4bb9de11 @ 15 : 5e fb f8 f7 01 CallUndefinedReceiver2 r0, r3, r4, [1]
0x2a0c4bb9de16 @ 20 : 26 fa Star r1
0x2a0c4bb9de18 @ 22 : 0d LdaUndefined
108 S> 0x2a0c4bb9de19 @ 23 : a9 Return
Constant pool (size = 1)
0x2a0c4bb9dd91: [FixedArray] in OldSpace
- map: 0x2a0c323807b1 <Map>
- length: 1
0: 0x2a0c4bb9dd29 <SharedFunctionInfo find_me_add>
Handler Table (size = 0)
[generated bytecode for function: find_me_add]
Parameter count 3
Frame size 0
48 E> 0x2a0c4bb9df3a @ 0 : a5 StackCheck
61 S> 0x2a0c4bb9df3b @ 1 : 25 02 Ldar a1
70 E> 0x2a0c4bb9df3d @ 3 : 34 03 00 Add a0, [0]
73 S> 0x2a0c4bb9df40 @ 6 : a9 Return
Constant pool (size = 0)
Handler Table (size = 0)
開頭利用 CreateClosure [0], [0], #2
去建立了一個 closure 然後去呼叫他。接下來是 function expression 的版本:
function find_me_test() {
var find_me_add = function(a, b) {
return a + b
}
var ret = find_me_add(1, 2)
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 40
21 E> 0x24d2d6f9de02 @ 0 : a5 StackCheck
46 S> 0x24d2d6f9de03 @ 1 : 81 00 00 02 CreateClosure [0], [0], #2
0x24d2d6f9de07 @ 5 : 26 fb Star r0
96 S> 0x24d2d6f9de09 @ 7 : 0c 01 LdaSmi [1]
0x24d2d6f9de0b @ 9 : 26 f8 Star r3
0x24d2d6f9de0d @ 11 : 0c 02 LdaSmi [2]
0x24d2d6f9de0f @ 13 : 26 f7 Star r4
96 E> 0x24d2d6f9de11 @ 15 : 5e fb f8 f7 01 CallUndefinedReceiver2 r0, r3, r4, [1]
0x24d2d6f9de16 @ 20 : 26 fa Star r1
0x24d2d6f9de18 @ 22 : 0d LdaUndefined
114 S> 0x24d2d6f9de19 @ 23 : a9 Return
Constant pool (size = 1)
0x24d2d6f9dd91: [FixedArray] in OldSpace
- map: 0x24d2f78807b1 <Map>
- length: 1
0: 0x24d2d6f9dd29 <SharedFunctionInfo find_me_add>
Handler Table (size = 0)
[generated bytecode for function: find_me_add]
Parameter count 3
Frame size 0
54 E> 0x24d2d6f9df3a @ 0 : a5 StackCheck
67 S> 0x24d2d6f9df3b @ 1 : 25 02 Ldar a1
76 E> 0x24d2d6f9df3d @ 3 : 34 03 00 Add a0, [0]
79 S> 0x24d2d6f9df40 @ 6 : a9 Return
Constant pool (size = 0)
Handler Table (size = 0)
乍看之下跟 function declaration 產生的結果一模一樣,但其實有一個超級微妙的差異:
function declaration
0x2a0c4bb9de02 @ 0 : 81 00 00 02 CreateClosure [0], [0], #2
0x2a0c4bb9de06 @ 4 : 26 fb Star r0
21 E> 0x2a0c4bb9de08 @ 6 : a5 StackCheck
function expression
21 E> 0x24d2d6f9de02 @ 0 : a5 StackCheck
46 S> 0x24d2d6f9de03 @ 1 : 81 00 00 02 CreateClosure [0], [0], #2
0x24d2d6f9de07 @ 5 : 26 fb Star r0
有看到差異嗎?差異就在於 StackCheck
出現的先後順序。若是把 StackCheck
視為執行這個函式的第一個步驟,就表示 function expression
是先開始執行函式,然後在呼叫 CreateClosure
,而 function declaration
則是先 CreateClosure
,再開始執行函式。
為什麼會有這個差異呢?這就是俗稱的 hoisting!這一點我們會在之後 Day06 的經典案例仔細研究一下,這邊就先不多說了。
接下來試試看調換一下順序,應該會出錯:
function find_me_test() {
var ret = find_me_add(1, 2)
var find_me_add = function(a, b) {
return a + b
}
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 40
21 E> 0x26688bc9de02 @ 0 : a5 StackCheck
38 S> 0x26688bc9de03 @ 1 : 0c 01 LdaSmi [1]
0x26688bc9de05 @ 3 : 26 f8 Star r3
0x26688bc9de07 @ 5 : 0c 02 LdaSmi [2]
0x26688bc9de09 @ 7 : 26 f7 Star r4
38 E> 0x26688bc9de0b @ 9 : 5e fa f8 f7 00 CallUndefinedReceiver2 r1, r3, r4, [0]
0x26688bc9de10 @ 14 : 26 fb Star r0
76 S> 0x26688bc9de12 @ 16 : 81 00 02 02 CreateClosure [0], [2], #2
0x26688bc9de16 @ 20 : 26 fa Star r1
0x26688bc9de18 @ 22 : 0d LdaUndefined
114 S> 0x26688bc9de19 @ 23 : a9 Return
Constant pool (size = 1)
0x26688bc9dd91: [FixedArray] in OldSpace
- map: 0x26681ab007b1 <Map>
- length: 1
0: 0x26688bc9dd29 <SharedFunctionInfo find_me_add>
Handler Table (size = 0)
a.js:2: TypeError: find_me_add is not a function
var ret = find_me_add(1, 2)
^
TypeError: find_me_add is not a function
at find_me_test (a.js:2:13)
at a.js:8:1
跟想像中的一樣,原本 CreateClosure
那段被移到後面去了,所以產生了 TypeError: find_me_add is not a function
。
箭頭函式
來看一下箭頭函式會不會不太一樣:
function find_me_test() {
var find_me_add = (a, b) => {
return a + b
}
var ret = find_me_add(1, 2)
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 40
21 E> 0xd261119dd4a @ 0 : a5 StackCheck
46 S> 0xd261119dd4b @ 1 : 81 00 00 02 CreateClosure [0], [0], #2
0xd261119dd4f @ 5 : 26 fb Star r0
91 S> 0xd261119dd51 @ 7 : 0c 01 LdaSmi [1]
0xd261119dd53 @ 9 : 26 f8 Star r3
0xd261119dd55 @ 11 : 0c 02 LdaSmi [2]
0xd261119dd57 @ 13 : 26 f7 Star r4
91 E> 0xd261119dd59 @ 15 : 5e fb f8 f7 01 CallUndefinedReceiver2 r0, r3, r4, [1]
0xd261119dd5e @ 20 : 26 fa Star r1
0xd261119dd60 @ 22 : 0d LdaUndefined
109 S> 0xd261119dd61 @ 23 : a9 Return
Constant pool (size = 1)
0xd261119dcd9: [FixedArray] in OldSpace
- map: 0x0d26c87807b1 <Map>
- length: 1
0: 0x0d261119dc71 <SharedFunctionInfo find_me_add>
Handler Table (size = 0)
[generated bytecode for function: find_me_add]
Parameter count 3
Frame size 0
46 E> 0xd261119de82 @ 0 : a5 StackCheck
62 S> 0xd261119de83 @ 1 : 25 02 Ldar a1
71 E> 0xd261119de85 @ 3 : 34 03 00 Add a0, [0]
74 S> 0xd261119de88 @ 6 : a9 Return
Constant pool (size = 0)
Handler Table (size = 0)
好,完全一模一樣。
IIFE
接著來看一下 IIFE:
function find_me_test() {
(function find_me_add(a, b) {
return a + b
})(1, 2)
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
21 E> 0x4abc69dd62 @ 0 : a5 StackCheck
28 S> 0x4abc69dd63 @ 1 : 81 00 00 02 CreateClosure [0], [0], #2
0x4abc69dd67 @ 5 : 26 fb Star r0
0x4abc69dd69 @ 7 : 0c 01 LdaSmi [1]
0x4abc69dd6b @ 9 : 26 fa Star r1
0x4abc69dd6d @ 11 : 0c 02 LdaSmi [2]
0x4abc69dd6f @ 13 : 26 f9 Star r2
79 E> 0x4abc69dd71 @ 15 : 5e fb fa f9 01 CallUndefinedReceiver2 r0, r1, r2, [1]
0x4abc69dd76 @ 20 : 0d LdaUndefined
86 S> 0x4abc69dd77 @ 21 : a9 Return
Constant pool (size = 1)
0x4abc69dcf1: [FixedArray] in OldSpace
- map: 0x004a540007b1 <Map>
- length: 1
0: 0x004abc69dca9 <SharedFunctionInfo find_me_add>
Handler Table (size = 0)
[generated bytecode for function: find_me_add]
Parameter count 3
Frame size 0
49 E> 0x4abc69ddf2 @ 0 : a5 StackCheck
62 S> 0x4abc69ddf3 @ 1 : 25 02 Ldar a1
71 E> 0x4abc69ddf5 @ 3 : 34 03 00 Add a0, [0]
74 S> 0x4abc69ddf8 @ 6 : a9 Return
Constant pool (size = 0)
Handler Table (size = 0)
好,還是一模一樣。
this
接著來看看各種 this 相關的操作好了:
function find_me_test() {
find_me_this()
find_me_this.call('a1')
find_me_this.apply('a2')
}
function find_me_this() {
console.log(this)
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
21 E> 0x35270c81de0a @ 0 : a5 StackCheck
28 S> 0x35270c81de0b @ 1 : 13 00 00 LdaGlobal [0], [0]
0x35270c81de0e @ 4 : 26 fb Star r0
28 E> 0x35270c81de10 @ 6 : 5c fb 02 CallUndefinedReceiver0 r0, [2]
45 S> 0x35270c81de13 @ 9 : 13 00 00 LdaGlobal [0], [0]
0x35270c81de16 @ 12 : 26 fa Star r1
58 E> 0x35270c81de18 @ 14 : 28 fa 01 04 LdaNamedProperty r1, [1], [4]
0x35270c81de1c @ 18 : 26 fb Star r0
0x35270c81de1e @ 20 : 12 02 LdaConstant [2]
0x35270c81de20 @ 22 : 26 f9 Star r2
58 E> 0x35270c81de22 @ 24 : 59 fb fa f9 06 CallProperty1 r0, r1, r2, [6]
71 S> 0x35270c81de27 @ 29 : 13 00 00 LdaGlobal [0], [0]
0x35270c81de2a @ 32 : 26 fa Star r1
84 E> 0x35270c81de2c @ 34 : 28 fa 03 08 LdaNamedProperty r1, [3], [8]
0x35270c81de30 @ 38 : 26 fb Star r0
0x35270c81de32 @ 40 : 12 04 LdaConstant [4]
0x35270c81de34 @ 42 : 26 f9 Star r2
84 E> 0x35270c81de36 @ 44 : 59 fb fa f9 0a CallProperty1 r0, r1, r2, [10]
0x35270c81de3b @ 49 : 0d LdaUndefined
96 S> 0x35270c81de3c @ 50 : a9 Return
Constant pool (size = 5)
0x35270c81dd71: [FixedArray] in OldSpace
- map: 0x3527f27007b1 <Map>
- length: 5
0: 0x35270c81d8e9 <String[#12]: find_me_this>
1: 0x3527f2703739 <String[#4]: call>
2: 0x35270c81dce1 <String[#2]: a1>
3: 0x3527f2703499 <String[#5]: apply>
4: 0x35270c81dcf9 <String[#2]: a2>
Handler Table (size = 0)
[generated bytecode for function: find_me_this]
Parameter count 1
Frame size 16
120 E> 0x35270c81dfc2 @ 0 : a5 StackCheck
127 S> 0x35270c81dfc3 @ 1 : 13 00 00 LdaGlobal [0], [0]
0x35270c81dfc6 @ 4 : 26 fa Star r1
135 E> 0x35270c81dfc8 @ 6 : 28 fa 01 02 LdaNamedProperty r1, [1], [2]
0x35270c81dfcc @ 10 : 26 fb Star r0
135 E> 0x35270c81dfce @ 12 : 59 fb fa 02 04 CallProperty1 r0, r1, <this>, [4]
0x35270c81dfd3 @ 17 : 0d LdaUndefined
145 S> 0x35270c81dfd4 @ 18 : a9 Return
Constant pool (size = 2)
0x35270c81df49: [FixedArray] in OldSpace
- map: 0x3527f27007b1 <Map>
- length: 2
0: 0x3527557900e9 <String[#7]: console>
1: 0x35275578fbe9 <String[#3]: log>
Handler Table (size = 0)
[object global]
a1
a2
在 bytecode 裡面就用了<this>
來表示 this,所以光看 bytecode 是沒辦法看出 this 的值是什麼的。
而呼叫 call
跟 apply
的方式是用 LdaNamedProperty
,因為這兩個都是 function find_me_this
上的屬性。
function constructor
來試試看比較特別的執行 function 的方法:
function find_me_test() {
var find_me_fn = new Function('console.log(1)')
find_me_fn()
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
21 E> 0xb1ed9e1dcd2 @ 0 : a5 StackCheck
45 S> 0xb1ed9e1dcd3 @ 1 : 13 00 00 LdaGlobal [0], [0]
0xb1ed9e1dcd6 @ 4 : 26 fa Star r1
0xb1ed9e1dcd8 @ 6 : 12 01 LdaConstant [1]
0xb1ed9e1dcda @ 8 : 26 f9 Star r2
0xb1ed9e1dcdc @ 10 : 25 fa Ldar r1
45 E> 0xb1ed9e1dcde @ 12 : 65 fa f9 01 02 Construct r1, r2-r2, [2]
0xb1ed9e1dce3 @ 17 : 26 fb Star r0
78 S> 0xb1ed9e1dce5 @ 19 : 5c fb 04 CallUndefinedReceiver0 r0, [4]
0xb1ed9e1dce8 @ 22 : 0d LdaUndefined
91 S> 0xb1ed9e1dce9 @ 23 : a9 Return
Constant pool (size = 2)
0xb1ed9e1dc59: [FixedArray] in OldSpace
- map: 0x0b1e83d007b1 <Map>
- length: 2
0: 0x0b1e83d03bd1 <String[#8]: Function>
1: 0x0b1ed9e1dbd9 <String[#14]: console.log(1)>
Handler Table (size = 0)
1
把 Function 從 global 載入以後,用一個專門的指令Construct
來呼叫 constructor,建立出 function 以後存到 r0 再呼叫。
最後來試一個特別的好了,不用任何 function 關鍵字執行 function:
function find_me_test() {
var find_me_fn = Array.prototype.slice.constructor('console.log(1)')
find_me_fn()
}
find_me_test()
結果:
[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 32
21 E> 0x3386c8b9dcf2 @ 0 : a5 StackCheck
45 S> 0x3386c8b9dcf3 @ 1 : 13 00 00 LdaGlobal [0], [0]
0x3386c8b9dcf6 @ 4 : 26 f9 Star r2
51 E> 0x3386c8b9dcf8 @ 6 : 28 f9 01 02 LdaNamedProperty r2, [1], [2]
0x3386c8b9dcfc @ 10 : 26 f9 Star r2
61 E> 0x3386c8b9dcfe @ 12 : 28 f9 02 04 LdaNamedProperty r2, [2], [4]
0x3386c8b9dd02 @ 16 : 26 f9 Star r2
67 E> 0x3386c8b9dd04 @ 18 : 28 f9 03 06 LdaNamedProperty r2, [3], [6]
0x3386c8b9dd08 @ 22 : 26 fa Star r1
0x3386c8b9dd0a @ 24 : 12 04 LdaConstant [4]
0x3386c8b9dd0c @ 26 : 26 f8 Star r3
67 E> 0x3386c8b9dd0e @ 28 : 59 fa f9 f8 08 CallProperty1 r1, r2, r3, [8]
0x3386c8b9dd13 @ 33 : 26 fb Star r0
99 S> 0x3386c8b9dd15 @ 35 : 5c fb 0a CallUndefinedReceiver0 r0, [10]
0x3386c8b9dd18 @ 38 : 0d LdaUndefined
112 S> 0x3386c8b9dd19 @ 39 : a9 Return
Constant pool (size = 5)
0x3386c8b9dc59: [FixedArray] in OldSpace
- map: 0x33868d6007b1 <Map>
- length: 5
0: 0x33868d603519 <String[#5]: Array>
1: 0x33868d604371 <String[#9]: prototype>
2: 0x3386b7c0afe9 <String[#5]: slice>
3: 0x33868d603851 <String[#11]: constructor>
4: 0x3386c8b9dbd9 <String[#14]: console.log(1)>
Handler Table (size = 0)
1
沒什麼特別的,就是不斷 LdaNamedProperty
而已。