6

ネイティブの forEach は、小さな配列でも遅すぎる場合があることに気付きました。この例を見てください:

var a = [], b = []; 
a[1234567] = 'foo'; 
b[10] = 'bar'; 

a.forEach(function(arg1, arg2) { console.log(arg1, arg2); }); //1
//vs 
b.forEach(function(arg1, arg2) { console.log(arg1, arg2); }); //2

私のChromium(25.0.1364.160 Ubuntu 12.04)では、1行目と2行目の実行時間は桁違いです。a の長さは 1234568 に等しく、bの長さはちょうど 10 に等しいことを私は知っています。しかし、ネイティブの forEach 実装はそれほど素朴ですか? abはどちらも1 つの要素のみで構成されています。この動作はどのように説明できますか?

4

3 に答える 3

10

これは、aの長さが実際には 1234568 であるため、1234568 個の要素をループする必要があるためです。要素が存在しないことを確認するにはどうすればよいでしょうか。

var a = []
a[1234567] = 'foo'
console.log(a.length) // 1234568

したがって、1234566 の none と 1 をループしていますが'foo'、配列bは 9 つの "nothing" と のみをループしてい'bar'ます。

foreachを読み取ろうとすると、index に何もないa[0]ため、そこにないことがわかります。したがって、次のように考えます。a0foreach

I'm going to see what a[0] is!
Oh no! It's not there!
I'm going to see what a[1] is!
Oh no! It's not there!
I'm going to see what a[2] is!
Oh no! It's not there!
...
I'm going to see what a[1234567] is!
Yaaaaay! I found it! Now I'll print it!

そのため、非常に時間がかかります。

于 2013-06-08T13:25:10.347 に答える
5

forEach途中で存在しない要素をスキップして、配列の全長にわたって反復します。abには 1 つの要素しか含まれていませんが、それらはlength大きいためforEach、反復するのに苦労しています。

やっぱり仕様ですね!ES5 15.4.4.18 Array.prototype.forEachの抜粋:

6) k を 0 とする。

7) k < len の間、繰り返す

これを実証するために、Firefox の SpiderMonkey がこれらのステップをどのように実装しているかを見てみましょう。

/* Steps 6-7. */
/* Steps a (implicit), and d. */
for (var k = 0; k < len; k++) {
    /* Step b */
    if (k in O) {
        /* Step c. */
        callFunction(callbackfn, T, O[k], k, O);
    }
}

kfrom 0toのループがlenパフォーマンスの問題の根底にあることがはっきりとわかります。ほぼすべてのk,k in Oが得られfalseますが、100 万回の反復と 100 万回のk in Oテストの影響を感じることができます。

参考までに、執筆時点でのSpiderMonkeyV8の実装へのリンクを以下に示します。

于 2013-06-08T13:25:39.973 に答える
4

しかし、ネイティブforEach実装はナイーブですか? abも1 つの要素のみから構成されます。この振る舞い[…]はどのように説明できますか?

ECMAScript 言語仕様、5.1 版、§15.4.4.18 で次のように指定されています。

forEachメソッドが 1 つまたは 2 つの引数で呼び出されると、次の手順が実行されます。

  1. この値を引数として渡して呼び出した結果をOとします。ToObject
  2. lenValueを引数でO[[Get]]の内部メソッドを呼び出した結果とします。"length"
  3. lenをlenValueとしますToUint32()
  4. callbackfnfalseの場合、例外をスローします。IsCallable()TypeError
  5. thisArgが指定された場合、 TthisArgとします。そうでなければT未定義にします。
  6. kを0とします。
  7. k < lenの間、繰り返します

    を。PkをkとするToString()

    b. kPresentを引数PkでOの内部メソッドを呼び出した結果とする[[HasProperty]]

    c. kPresenttrue の場合、

    私。kValueを引数PkでOの内部メソッドを呼び出した結果とします。[[Get]]

    ii. this値としてTを指定し、 kValuek、およびOを含む引数リストを指定して、 callbackfn[[Call]]の内部メソッドを呼び出します。

    d. kを 1増やします。

  8. undefinedを返します。

ステップ 6 と 7では、最初のインデックスから最後のインデックスまで、そのインデックスを持つ配列要素があるかどうかに関係なく、すべてのインデックスを反復処理する適合実装が必要です。これは、最後のインデックスが大きい場合にメソッド呼び出しに時間がかかる理由を説明しています。0a.length - 1forEach

ただし、手順 7b (の実装) では、[[HasProperty]]内部メソッドは配列要素がないインデックスに対してfalseを返す必要があるため、それらのインデックスに対してコールバックcallbackfnは呼び出されません。これは、メソッド呼び出しconsole.logを実行するときに呼び出しが 1 つしかない理由を説明しています。forEach

于 2013-06-08T14:02:25.180 に答える