web-dev-qa-db-ja.com

激しいJavaScriptループがブラウザをフリーズさせないようにする方法

Javascriptを使用して、約3,500個の要素を持つXMLファイルを解析しています。 jQueryの「各」関数を使用していますが、任意の形式のループを使用できます。
問題は、ループの実行中にブラウザが数秒間フリーズすることです。コードの速度を落とさずにブラウザのフリーズを止める最良の方法は何ですか?

$(xmlDoc).find("Object").each(function() {
    //Processing here
});
56
Chris B

Forループの方が速いため、「each」関数を捨てます。 「setTimeout」を使用して待機を追加することもありますが、必要な場合にのみ頻繁に追加します。 3500レコードの処理には約17.5秒かかるため、毎回5ミリ秒待機する必要はありません。

以下は、100のレコードを処理するforループを使用した例です(5ミリ秒間隔で調整できます)。これにより、175ミリ秒のオーバーヘッドが発生します。

var xmlElements = $(xmlDoc).find('Object');
var length = xmlElements.length;
var index = 0;
var process = function() {
  for (; index < length; index++) {
    var toProcess = xmlElements[index];
    // Perform xml processing
    if (index + 1 < length && index % 100 == 0) {
        setTimeout(process, 5);
    }
  }
};
process();

また、XML処理のさまざまな部分のベンチマークを行い、修正可能なボトルネックがあるかどうかを確認します。 firebugのプロファイラーを使用し、次のようにコンソールに書き込むことにより、firefoxでベンチマークを実行できます。

// start benchmark
var t = new Date();
// some xml processing
console.log("Time to process: " + new Date() - t + "ms");

お役に立てれば。

68
Helgi

処理の間にtimeOutを設定して、ループサイクルがすべてのブラウザリソースを使い果たすことを防ぎます。合計すると、すべてを処理してループするのに数秒しかかからず、3,500の要素に対しては不合理ではありません。

var xmlElements = $(xmlDoc).find('Object');

var processing = function() {
  var element = xmlElements.shift();

  //process element;

  if (xmlElements.length > 0) {
    setTimeout(processing, 5);
  }
}

processing();
22
tj111

GetgoからJSにネイティブであるように、3500要素をxmlからJSONサーバーサイドに変換するか、変換したサーバーにアップロードすることを検討します。

これにより、負荷が最小限に抑えられ、ファイルサイズも小さくなります。

6

Turboidフレームワークを使用すると、ブラウザーをフリーズせずに長いループを作成できます。これにより、次のようなコードを作成できます。

loop(function(){  
        // Do something...  
}, number_of_iterations, number_of_milliseconds);

このturboid.net記事の詳細: Javascriptの実際のループ

3
sharp solution

ゼロの期間でsetTimeout()を設定すると、必要に応じて生成されます

2
Scott Evernden

Javascriptはシングルスレッドなので、setTimeoutは別として、できることはあまりありません。 Google Gearsを使用することがサイトのオプションである場合、真のバックグラウンドスレッドでjavascriptを実行する機能を提供します。

2
Gabe Moothart

HTML5ワーカーAPIを使用できますが、これはFirefox 3.1およびSafari 4ベータatmでのみ機能します。

1
olliej

あなたはコードを短くすることを試みることができます

   $(xmlDoc).find("Object").each(function(arg1) {
    (function(arg1_received) {
                setTimeout(function(arg1_received_reached) {

                    //your stuff with the arg1_received_reached goes here 

                }(arg1_received), 0)
            })(arg1)
}(this));

これはあまりあなたに害を与えません;)

1
LINTUism

ユーザーがページを連続して更新したときにも同じ問題が発生しました。その理由は、2つのネストされたforループが52000回以上発生したためです。この問題は、Firefox 24でChrome 29よりもFirefoxが早くクラッシュする(Chromeよりも約2000ミリ秒早く)ので、より厳しいものでした。それぞれのコードをリファクタリングして、ループ配列全体を4つの別々の呼び出しに分割し、結果を1つにマージしました。

このようなもの:

var entittiesToLoop = ["..."]; // Mainly a big array
   loopForSubset(0, firstInterval);
   loopForSubset(firstInterval, secondInterval);
    ...

var loopForSubset = function (startIndex, endIndex) {
    for (var i=startIndex; i < endIndex; i++) {
            //Do your stuff as usual here
    }
}

私のために働いた他のソリューションは、Worker APIs HTML5から。ワーカーでメインスレッドのバックグラウンドで実行されるため、ブラウザーがフリーズするのを防ぐため、ワーカーで同じコンセプトを使用します。これをWorkers APIで適用してもうまくいかない場合は、loopForSubsetの各インスタンスを異なるワーカーに配置し、結果をWorkerのメインの呼び出し元にマージします。

これは完璧ではないかもしれませんが、これはうまくいきました。誰かがまだこれがそれらに合うかもしれないと思うなら、私はより本当のコードチャンクを手伝うことができます。

1
FidEliO

@ tj111の修正として、使用可能なコード全体に回答する

    //add pop and shift functions to jQuery library. put in somewhere in your code.
    //pop function is now used here but you can use it in other parts of your code.
    (function( $ ) {
        $.fn.pop = function() {
            var top = this.get(-1);
            this.splice(this.length-1,1);
            return top;
        };

        $.fn.shift = function() {
            var bottom = this.get(0);
            this.splice(0,1);
            return bottom;
        };
    })( jQuery );


//the core of the code:
    var $div = $('body').find('div');//.each();
    var s= $div.length;
    var mIndex = 0;
    var process = function() {
        var $div = $div.first();            
    //here your own code.

    //progress bar:
        mIndex++;
    // e.g.:    progressBar(mIndex/s*100.,$pb0);

    //start new iteration.
        $div.shift();
        if($div.size()>0){
            setTimeout(process, 5);
        } else {
    //when calculations are finished.
            console.log('finished');
        }
    }
    process();
0
Vyacheslav