Day03:從迴圈看 bytecode


前言

前面已經看過變數與判斷式了,這一篇讓我們來看看迴圈在 V8 中是如何被實現的,看一下 for loop 與 while loop 有沒有區別。對於初學者來說,其實觀看迴圈的 bytecode 也能更理解迴圈的運作。

for loop

一樣先從最簡單的開始:

function find_me_test() {
  for(var i=1; i<=10; i++) {
    console.log(i)
  }
}

find_me_test()

產生的 bytecode:

[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
   21 E> 0xd70b3a1dcb2 @    0 : a5                StackCheck 
   38 S> 0xd70b3a1dcb3 @    1 : 0c 01             LdaSmi [1]
         0xd70b3a1dcb5 @    3 : 26 fb             Star r0
   42 S> 0xd70b3a1dcb7 @    5 : 0c 0a             LdaSmi [10]
   42 E> 0xd70b3a1dcb9 @    7 : 6b fb 00          TestLessThanOrEqual r0, [0]
         0xd70b3a1dcbc @   10 : 99 1c             JumpIfFalse [28] (0xd70b3a1dcd8 @ 38)
   28 E> 0xd70b3a1dcbe @   12 : a5                StackCheck 
   59 S> 0xd70b3a1dcbf @   13 : 13 00 01          console.log(i)
   49 S> 0xd70b3a1dccf @   29 : 25 fb             Ldar r0
         0xd70b3a1dcd1 @   31 : 4c 07             Inc [7]
         0xd70b3a1dcd3 @   33 : 26 fb             Star r0
         0xd70b3a1dcd5 @   35 : 8a 1e 00          JumpLoop [30], [0] (0xd70b3a1dcb7 @ 5)
         0xd70b3a1dcd8 @   38 : 0d                LdaUndefined 
   78 S> 0xd70b3a1dcd9 @   39 : a9                Return 
Constant pool (size = 2)
0xd70b3a1dc31: [FixedArray] in OldSpace
 - map: 0x0d70563007b1 <Map>
 - length: 2
           0: 0x0d70930900e9 <String[#7]: console>
           1: 0x0d709308fbe9 <String[#3]: log>
Handler Table (size = 0)
1
2
3
4
5
6
7
8
9
10

在 V8 裡面用了 JumpLoop 來實作迴圈,而這個結構也是常見的組合語言實作迴圈的結構,所以看習慣組合語言的人看這個應該不陌生。而且比起組合語言,bytecode 還貼心的直接用 JumpLoop 而不是一般的 Jump 來標明這是一個迴圈。

另外一個值得注意的地方是除了第一圈以外,永遠都會先 i++ 再進行條件檢查,因此當這個迴圈跑完以後,i 的值會是 11,這是有些初學者不會注意到的事情。從上面 bytecode 就可以看出來,迴圈的執行順序是:

  1. 初始條件(var i = 1)
  2. 判斷式(if i <= 10)
  3. 如果結果為 false,結束
  4. 執行迴圈裡面要做的事(console.log(i))
  5. 跑完一圈要做的事(i++)
  6. 跳到第 2 行

接著我們來試試看巢狀迴圈:

function find_me_test() {
  for(var i=1; i<=3; i++) {
    for(var j=1; j<=3; j++) {
      console.log(i, j)
    }
  }
}

find_me_test()

產生的 bytecode:

[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 32
   21 E> 0xb35cd59dcda @    0 : a5                StackCheck 
   38 S> 0xb35cd59dcdb @    1 : 0c 01             LdaSmi [1]
         0xb35cd59dcdd @    3 : 26 fb             Star r0
   42 S> 0xb35cd59dcdf @    5 : 0c 03             LdaSmi [3]
   42 E> 0xb35cd59dce1 @    7 : 6b fb 00          TestLessThanOrEqual r0, [0]
         0xb35cd59dce4 @   10 : 99 32             JumpIfFalse [50] (0xb35cd59dd16 @ 60)
   28 E> 0xb35cd59dce6 @   12 : a5                StackCheck 
   68 S> 0xb35cd59dce7 @   13 : 0c 01             LdaSmi [1]
         0xb35cd59dce9 @   15 : 26 fa             Star r1
   72 S> 0xb35cd59dceb @   17 : 0c 03             LdaSmi [3]
   72 E> 0xb35cd59dced @   19 : 6b fa 01          TestLessThanOrEqual r1, [1]
         0xb35cd59dcf0 @   22 : 99 1d             JumpIfFalse [29] (0xb35cd59dd0d @ 51)
   58 E> 0xb35cd59dcf2 @   24 : a5                StackCheck 
   90 S> 0xb35cd59dcf3 @   25 : 13 00 02          console.log(i, j)
   78 S> 0xb35cd59dd04 @   42 : 25 fa             Ldar r1
         0xb35cd59dd06 @   44 : 4c 08             Inc [8]
         0xb35cd59dd08 @   46 : 26 fa             Star r1
         0xb35cd59dd0a @   48 : 8a 1f 01          JumpLoop [31], [1] (0xb35cd59dceb @ 17)
   48 S> 0xb35cd59dd0d @   51 : 25 fb             Ldar r0
         0xb35cd59dd0f @   53 : 4c 09             Inc [9]
         0xb35cd59dd11 @   55 : 26 fb             Star r0
         0xb35cd59dd13 @   57 : 8a 34 00          JumpLoop [52], [0] (0xb35cd59dcdf @ 5)
         0xb35cd59dd16 @   60 : 0d                LdaUndefined 
  118 S> 0xb35cd59dd17 @   61 : a9                Return 
Constant pool (size = 2)
0xb35cd59dc49: [FixedArray] in OldSpace
 - map: 0x0b3588b807b1 <Map>
 - length: 2
           0: 0x0b359ac100e9 <String[#7]: console>
           1: 0x0b359ac0fbe9 <String[#3]: log>
Handler Table (size = 0)
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3

值得注意的地方是跳轉時候的參數:

  1. JumpLoop [31], [1] (0xb35cd59dceb @ 17)
  2. JumpLoop [52], [0] (0xb35cd59dcdf @ 5)

可以從第二個參數 [1] 或是 [0] 來辨別是第幾層迴圈的跳轉,詳情可以從 V8 source code 上面看到:

// JumpLoop <imm> <loop_depth>
//
// Jump by the number of bytes represented by the immediate operand |imm|. Also
// performs a loop nesting check and potentially triggers OSR in case the
// current OSR level matches (or exceeds) the specified |loop_depth|.

這還滿方便的,若是沒有這個參數的話,很難從 bytecode 直接看出是哪一個迴圈的跳轉。

那如果是無窮迴圈呢?會產生怎樣的程式碼?

function find_me_test() {
  for(;;) {
    console.log(1)
  }
}

find_me_test()

結果:

[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
   21 E> 0x13b785a1dc92 @    0 : a5                StackCheck 
   28 E> 0x13b785a1dc93 @    1 : a5                StackCheck 
   42 S> 0x13b785a1dc94 @    2 : 13 00 00          console.log(1)
         0x13b785a1dca8 @   22 : 8a 15 00          JumpLoop [21], [0] (0x13b785a1dc93 @ 1)
         0x13b785a1dcab @   25 : 0d                LdaUndefined 
   61 S> 0x13b785a1dcac @   26 : a9                Return 
Constant pool (size = 2)
0x13b785a1dc19: [FixedArray] in OldSpace
 - map: 0x13b7e71807b1 <Map>
 - length: 2
           0: 0x13b73d7100e9 <String[#7]: console>
           1: 0x13b73d70fbe9 <String[#3]: log>
Handler Table (size = 0)

其實結構跟一般迴圈一樣,只是少了條件判斷的地方而已,會一直不斷的 JumpLoop。

while loop

接著我們來試試看 while loop,一樣列印出 1~10,跟之前的 for 迴圈功能一模一樣:

function find_me_test() {
  var i = 1
  while(i <= 10) {
    console.log(i)
    i++
  }
}

find_me_test()

結果:

[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
   21 E> 0x30eb76b9dcb2 @    0 : a5                StackCheck 
   36 S> 0x30eb76b9dcb3 @    1 : 0c 01             LdaSmi [1]
         0x30eb76b9dcb5 @    3 : 26 fb             Star r0
   48 S> 0x30eb76b9dcb7 @    5 : 0c 0a             LdaSmi [10]
   48 E> 0x30eb76b9dcb9 @    7 : 6b fb 00          TestLessThanOrEqual r0, [0]
         0x30eb76b9dcbc @   10 : 99 1c             JumpIfFalse [28] (0x30eb76b9dcd8 @ 38)
   40 E> 0x30eb76b9dcbe @   12 : a5                StackCheck 
   61 S> 0x30eb76b9dcbf @   13 : 13 00 01          console.log(i)
   80 S> 0x30eb76b9dccf @   29 : 25 fb             Ldar r0
         0x30eb76b9dcd1 @   31 : 4c 07             Inc [7]
         0x30eb76b9dcd3 @   33 : 26 fb             Star r0
         0x30eb76b9dcd5 @   35 : 8a 1e 00          JumpLoop [30], [0] (0x30eb76b9dcb7 @ 5)
         0x30eb76b9dcd8 @   38 : 0d                LdaUndefined 
   88 S> 0x30eb76b9dcd9 @   39 : a9                Return 
Constant pool (size = 2)
0x30eb76b9dc31: [FixedArray] in OldSpace
 - map: 0x30eb9c6807b1 <Map>
 - length: 2
           0: 0x30eb10f100e9 <String[#7]: console>
           1: 0x30eb10f0fbe9 <String[#3]: log>
Handler Table (size = 0)
1
2
3
4
5
6
7
8
9
10

可以看到產生出來的 bytecode 與 for loop 產生出來的一模一樣,完全沒有差異。

那 do while 呢?

function find_me_test() {
  var i = 1
  do {
    console.log(i)
    i++
  } while(i <= 10)
}

find_me_test()

結果:

[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 24
   21 E> 0x25ff5eb1dcb2 @    0 : a5                StackCheck 
   36 S> 0x25ff5eb1dcb3 @    1 : 0c 01             LdaSmi [1]
         0x25ff5eb1dcb5 @    3 : 26 fb             Star r0
   40 E> 0x25ff5eb1dcb7 @    5 : a5                StackCheck 
   49 S> 0x25ff5eb1dcb8 @    6 : 13 00 00          console.log(i)
   68 S> 0x25ff5eb1dcc8 @   22 : 25 fb             Ldar r0
         0x25ff5eb1dcca @   24 : 4c 06             Inc [6]
         0x25ff5eb1dccc @   26 : 26 fb             Star r0
   84 S> 0x25ff5eb1dcce @   28 : 0c 0a             LdaSmi [10]
   84 E> 0x25ff5eb1dcd0 @   30 : 6b fb 07          TestLessThanOrEqual r0, [7]
         0x25ff5eb1dcd3 @   33 : 99 05             JumpIfFalse [5] (0x25ff5eb1dcd8 @ 38)
         0x25ff5eb1dcd5 @   35 : 8a 1e 00          JumpLoop [30], [0] (0x25ff5eb1dcb7 @ 5)
         0x25ff5eb1dcd8 @   38 : 0d                LdaUndefined 
   91 S> 0x25ff5eb1dcd9 @   39 : a9                Return 
Constant pool (size = 2)
0x25ff5eb1dc31: [FixedArray] in OldSpace
 - map: 0x25ff05f007b1 <Map>
 - length: 2
           0: 0x25ff939100e9 <String[#7]: console>
           1: 0x25ff9390fbe9 <String[#3]: log>
Handler Table (size = 0)
1
2
3
4
5
6
7
8
9
10

do while 的結構簡單一點,先做事情,然後把條件判斷以及 jump 放在最後面,bytecode 讀起來會簡單很多。

for in

既然都研究了迴圈,那可以連 for in 也順便研究一下:

function find_me_test() {
  var obj = {
    a: 1,
    b: 2,
    c: 3
  }
  for(var element in obj) {
    console.log(element)
  }
}

find_me_test()

結果:

[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 80
   21 E> 0x29fa8f81dd4a @    0 : a5                StackCheck 
   38 S> 0x29fa8f81dd4b @    1 : 7d 00 00 29       CreateObjectLiteral [0], [0], #41
         0x29fa8f81dd4f @    5 : 26 fb             Star r0
   94 S> 0x29fa8f81dd51 @    7 : 9c 36             JumpIfUndefined [54] (0x29fa8f81dd87 @ 61)
         0x29fa8f81dd53 @    9 : 9a 34             JumpIfNull [52] (0x29fa8f81dd87 @ 61)
         0x29fa8f81dd55 @   11 : 77 f8             ToObject r3
         0x29fa8f81dd57 @   13 : a0 f8             ForInEnumerate r3
         0x29fa8f81dd59 @   15 : a1 f7 01          ForInPrepare r4-r6, [1]
         0x29fa8f81dd5c @   18 : 0b                LdaZero 
         0x29fa8f81dd5d @   19 : 26 f4             Star r7
   83 S> 0x29fa8f81dd5f @   21 : a2 f4 f5          ForInContinue r7, r6
         0x29fa8f81dd62 @   24 : 99 25             JumpIfFalse [37] (0x29fa8f81dd87 @ 61)
         0x29fa8f81dd64 @   26 : a3 f8 f4 f7 01    ForInNext r3, r7, r4-r5, [1]
         0x29fa8f81dd69 @   31 : 9c 17             JumpIfUndefined [23] (0x29fa8f81dd80 @ 54)
         0x29fa8f81dd6b @   33 : 26 f9             Star r2
   75 E> 0x29fa8f81dd6d @   35 : a5                StackCheck 
   83 S> 0x29fa8f81dd6e @   36 : 26 fa             Star r1
  105 S> 0x29fa8f81dd70 @   38 : 13 01 02          console.log(r2)
         0x29fa8f81dd80 @   54 : a4 f4             ForInStep r7
         0x29fa8f81dd82 @   56 : 26 f4             Star r7
         0x29fa8f81dd84 @   58 : 8a 25 00          JumpLoop [37], [0] (0x29fa8f81dd5f @ 21)
         0x29fa8f81dd87 @   61 : 0d                LdaUndefined 
  130 S> 0x29fa8f81dd88 @   62 : a9                Return 
Constant pool (size = 3)
0x29fa8f81dcc1: [FixedArray] in OldSpace
 - map: 0x29fa1ea807b1 <Map>
 - length: 3
           0: 0x29fa8f81dc69 <ObjectBoilerplateDescription[7]>
           1: 0x29fad31900e9 <String[#7]: console>
           2: 0x29fad318fbe9 <String[#3]: log>
Handler Table (size = 0)
a
b
c

可以看到程式碼的部分滿特別的,出現了幾個與 for in 相關的指令:

  1. ForInEnumerate
  2. ForInPrepare
  3. ForInContinue
  4. ForInNext
  5. ForInStep

雖然說指令不太一樣,不過基本上流程還是滿好懂的,就跟一般迴圈差不多,只是指令換了。

for of

最後讓我們來看一下 for of 會產生出什麼樣的結果,一樣準備一個很簡單的資料:

function find_me_test() {
  var arr = [1, 2, 3]
  for(var element of arr) {
    console.log(element)
  }
}

find_me_test()

結果:

[generated bytecode for function: find_me_test]
Parameter count 1
Frame size 120
   21 E> 0x1b024c31dd22 @    0 : a5                StackCheck 
   38 S> 0x1b024c31dd23 @    1 : 7a 00 00 25       CreateArrayLiteral [0], [0], #37
         0x1b024c31dd27 @    5 : 26 fb             Star r0
   69 S> 0x1b024c31dd29 @    7 : 28 fb 01 01       LdaNamedProperty r0, [1], [1]
         0x1b024c31dd2d @   11 : 26 f5             Star r6
         0x1b024c31dd2f @   13 : 58 f5 fb 03       CallProperty0 r6, r0, [3]
         0x1b024c31dd33 @   17 : 27 fb f6          Mov r0, r5
         0x1b024c31dd36 @   20 : 9e 07             JumpIfJSReceiver [7] (0x1b024c31dd3d @ 27)
         0x1b024c31dd38 @   22 : 61 b6 00 fb 00    CallRuntime [ThrowSymbolIteratorInvalid], r0-r0
         0x1b024c31dd3d @   27 : 26 f7             Star r4
         0x1b024c31dd3f @   29 : 28 f7 02 05       LdaNamedProperty r4, [2], [5]
         0x1b024c31dd43 @   33 : 26 f8             Star r3
         0x1b024c31dd45 @   35 : 11                LdaFalse 
         0x1b024c31dd46 @   36 : 26 f4             Star r7
         0x1b024c31dd48 @   38 : 27 ff f1          Mov <context>, r10
         0x1b024c31dd4b @   41 : 10                LdaTrue 
         0x1b024c31dd4c @   42 : 26 f4             Star r7
   58 S> 0x1b024c31dd4e @   44 : 58 f8 f7 07       CallProperty0 r3, r4, [7]
         0x1b024c31dd52 @   48 : 26 f0             Star r11
         0x1b024c31dd54 @   50 : 9e 07             JumpIfJSReceiver [7] (0x1b024c31dd5b @ 57)
         0x1b024c31dd56 @   52 : 61 af 00 f0 01    CallRuntime [ThrowIteratorResultNotAnObject], r11-r11
         0x1b024c31dd5b @   57 : 28 f0 03 09       LdaNamedProperty r11, [3], [9]
         0x1b024c31dd5f @   61 : 96 28             JumpIfToBooleanTrue [40] (0x1b024c31dd87 @ 101)
         0x1b024c31dd61 @   63 : 28 f0 04 0b       LdaNamedProperty r11, [4], [11]
         0x1b024c31dd65 @   67 : 26 f0             Star r11
         0x1b024c31dd67 @   69 : 11                LdaFalse 
         0x1b024c31dd68 @   70 : 26 f4             Star r7
         0x1b024c31dd6a @   72 : 27 f0 f9          Mov r11, r2
   50 E> 0x1b024c31dd6d @   75 : a5                StackCheck 
   58 S> 0x1b024c31dd6e @   76 : 27 f9 fa          Mov r2, r1
   80 S> 0x1b024c31dd71 @   79 : 13 05 0d          LdaGlobal [5], [13]
         0x1b024c31dd74 @   82 : 26 ee             Star r13
   88 E> 0x1b024c31dd76 @   84 : 28 ee 06 0f       LdaNamedProperty r13, [6], [15]
         0x1b024c31dd7a @   88 : 26 ef             Star r12
   88 E> 0x1b024c31dd7c @   90 : 59 ef ee f9 11    CallProperty1 r12, r13, r2, [17]
         0x1b024c31dd81 @   95 : 27 fa f0          Mov r1, r11
         0x1b024c31dd84 @   98 : 8a 39 00          JumpLoop [57], [0] (0x1b024c31dd4b @ 41)
         0x1b024c31dd87 @  101 : 0c ff             LdaSmi [-1]
         0x1b024c31dd89 @  103 : 26 f2             Star r9
         0x1b024c31dd8b @  105 : 26 f3             Star r8
         0x1b024c31dd8d @  107 : 8b 07             Jump [7] (0x1b024c31dd94 @ 114)
         0x1b024c31dd8f @  109 : 26 f2             Star r9
         0x1b024c31dd91 @  111 : 0b                LdaZero 
         0x1b024c31dd92 @  112 : 26 f3             Star r8
         0x1b024c31dd94 @  114 : 0f                LdaTheHole 
         0x1b024c31dd95 @  115 : a6                SetPendingMessage 
         0x1b024c31dd96 @  116 : 26 f1             Star r10
         0x1b024c31dd98 @  118 : 25 f4             Ldar r7
         0x1b024c31dd9a @  120 : 96 3c             JumpIfToBooleanTrue [60] (0x1b024c31ddd6 @ 180)
         0x1b024c31dd9c @  122 : 28 f7 07 13       LdaNamedProperty r4, [7], [19]
         0x1b024c31dda0 @  126 : 26 ef             Star r12
         0x1b024c31dda2 @  128 : 9c 34             JumpIfUndefined [52] (0x1b024c31ddd6 @ 180)
         0x1b024c31dda4 @  130 : 9a 32             JumpIfNull [50] (0x1b024c31ddd6 @ 180)
         0x1b024c31dda6 @  132 : 73 06             TestTypeOf #6
         0x1b024c31dda8 @  134 : 98 12             JumpIfTrue [18] (0x1b024c31ddba @ 152)
         0x1b024c31ddaa @  136 : 00 0c 9a 00       LdaSmi.Wide [154]
         0x1b024c31ddae @  140 : 26 ee             Star r13
         0x1b024c31ddb0 @  142 : 12 08             LdaConstant [8]
         0x1b024c31ddb2 @  144 : 26 ed             Star r14
         0x1b024c31ddb4 @  146 : 61 9f 00 ee 02    CallRuntime [NewTypeError], r13-r14
         0x1b024c31ddb9 @  151 : a7                Throw 
         0x1b024c31ddba @  152 : 27 ff ee          Mov <context>, r13
         0x1b024c31ddbd @  155 : 58 ef f7 15       CallProperty0 r12, r4, [21]
         0x1b024c31ddc1 @  159 : 9e 15             JumpIfJSReceiver [21] (0x1b024c31ddd6 @ 180)
         0x1b024c31ddc3 @  161 : 26 ed             Star r14
         0x1b024c31ddc5 @  163 : 61 af 00 ed 01    CallRuntime [ThrowIteratorResultNotAnObject], r14-r14
         0x1b024c31ddca @  168 : 8b 0c             Jump [12] (0x1b024c31ddd6 @ 180)
         0x1b024c31ddcc @  170 : 26 ee             Star r13
         0x1b024c31ddce @  172 : 0b                LdaZero 
         0x1b024c31ddcf @  173 : 6d f3             TestReferenceEqual r8
         0x1b024c31ddd1 @  175 : 98 05             JumpIfTrue [5] (0x1b024c31ddd6 @ 180)
         0x1b024c31ddd3 @  177 : 25 ee             Ldar r13
         0x1b024c31ddd5 @  179 : a8                ReThrow 
         0x1b024c31ddd6 @  180 : 25 f1             Ldar r10
         0x1b024c31ddd8 @  182 : a6                SetPendingMessage 
         0x1b024c31ddd9 @  183 : 0b                LdaZero 
         0x1b024c31ddda @  184 : 6d f3             TestReferenceEqual r8
         0x1b024c31dddc @  186 : 99 05             JumpIfFalse [5] (0x1b024c31dde1 @ 191)
         0x1b024c31ddde @  188 : 25 f2             Ldar r9
         0x1b024c31dde0 @  190 : a8                ReThrow 
         0x1b024c31dde1 @  191 : 0d                LdaUndefined 
  105 S> 0x1b024c31dde2 @  192 : a9                Return 
Constant pool (size = 9)
0x1b024c31dc69: [FixedArray] in OldSpace
 - map: 0x1b02631007b1 <Map>
 - length: 9
           0: 0x1b024c31dc21 <ArrayBoilerplateDescription 0, 0x1b025400af19 <FixedArray[3]>>
           1: 0x1b0263104d79 <Symbol: Symbol.iterator>
           2: 0x1b02631040d9 <String[#4]: next>
           3: 0x1b0263103979 <String[#4]: done>
           4: 0x1b0263104959 <String[#5]: value>
           5: 0x1b02e45100e9 <String[#7]: console>
           6: 0x1b02e450fbe9 <String[#3]: log>
           7: 0x1b02631044c1 <String[#6]: return>
           8: 0x1b0263100751 <String[#0]: >
Handler Table (size = 32)
   from   to       hdlr (prediction,   data)
  (  41, 101)  ->   109 (prediction=0, data=10)
  ( 155, 168)  ->   170 (prediction=0, data=13)
1
2
3

一眼就可以發現程式碼比想像中長很多,不過從 constant pool 裡面的值可以立刻推測發生了什麼事:

 - length: 9
           0: 0x1b024c31dc21 <ArrayBoilerplateDescription 0, 0x1b025400af19 <FixedArray[3]>>
           1: 0x1b0263104d79 <Symbol: Symbol.iterator>
           2: 0x1b02631040d9 <String[#4]: next>
           3: 0x1b0263103979 <String[#4]: done>
           4: 0x1b0263104959 <String[#5]: value>
           5: 0x1b02e45100e9 <String[#7]: console>
           6: 0x1b02e450fbe9 <String[#3]: log>
           7: 0x1b02631044c1 <String[#6]: return>
           8: 0x1b0263100751 <String[#0]: >

for of 底層實作是透過 iterator 來做的,在 MDN 上面可以看見有一個自己實作的小範例:

const iterable = {
  [Symbol.iterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return { value: this.i++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (const value of iterable) {
  console.log(value);
}
// 0
// 1
// 2

所以 for of 基本上就是先透過 iterable[Symbol.iterator]() 拿到 iterator,然後不斷呼叫 iterator.next(),如果返回的物件 done 是 true 代表結束,否則的話 value 就是拿到的值,這個 pattern 其實就跟 generator 是一樣的。

理解了背後的原理以後,就可以把這一段 for of:

var arr = [1, 2, 3]
for(var element of arr) {
  console.log(element)
}

看作是這樣:

var arr = [1, 2, 3]
var it = arr[Symbol.iterator]()
var result = it.next()
while(!result.done) {
  console.log(result.value)
  result = it.next()
}

這也是為什麼 for of 產生的 bytecode 會那麼長的緣故。而且裡面有許多指令都在做檢查,例如說:CallRuntime [ThrowSymbolIteratorInvalid]CallRuntime [ThrowIteratorResultNotAnObject] 以及 CallRuntime [NewTypeError]

#javascript







你可能感興趣的文章

CS50 TCP (Transmission Control Protocol)

CS50 TCP (Transmission Control Protocol)

29 Incrementer 7 kyu

29 Incrementer 7 kyu

JavaScript變數&運算

JavaScript變數&運算






留言討論