Day04:從函式看 bytecode


前言

終於要進入比較有趣的主題啦!這一篇的內容會從各個角度去切入函式這個主題,一起來看看會被翻譯成怎樣的 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 的值是什麼的。

而呼叫 callapply 的方式是用 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 而已。

#javascript







你可能感興趣的文章

[Linux] Windows安裝Wsl2 + Ubuntu22.04 + Docker +Oracle

[Linux] Windows安裝Wsl2 + Ubuntu22.04 + Docker +Oracle

Web VR 初探

Web VR 初探

Progressive Web App 會是未來趨勢嗎?

Progressive Web App 會是未來趨勢嗎?






留言討論