Как остановить интенсивный цикл Javascript от замораживания браузера
Я использую Javascript для анализа XML-файла с около 3500 элементов. Я использую функцию jQuery "каждый", но я мог бы использовать любую форму цикла.
Проблема в том, что браузер зависает на несколько секунд во время выполнения цикла. Каков наилучший способ остановить замораживание браузера, не замедляя код слишком сильно?
$(xmlDoc).find("Object").each(function() {
//Processing here
});
10 ответов:
Я бы отказался от функции "каждый" в пользу цикла for, так как он быстрее. Я бы также добавил некоторые ожидания, используя "setTimeout", но только так часто и только в случае необходимости. Вы не хотите ждать 5 мс каждый раз, потому что тогда обработка 3500 записей займет около 17,5 секунд.
Ниже приведен пример использования цикла for, который обрабатывает 100 записей (вы можете настроить это) с интервалом 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, чтобы увидеть, если есть узкое место где-то, что может быть исправлено. Вы можете тестировать в firefox с помощью профилировщика firebug и писать на консоль следующим образом:
// start benchmark var t = new Date(); // some xml processing console.log("Time to process: " + new Date() - t + "ms");надеюсь, что это помогает.
установите тайм-аут между обработкой, чтобы предотвратить цикл цикла от поедания всех ресурсов браузера. В общей сложности это займет всего несколько секунд, чтобы обработать и перебрать все, не безосновательно за 3500 элементов.
var xmlElements = $(xmlDoc).find('Object'); var processing = function() { var element = xmlElements.shift(); //process element; if (xmlElements.length > 0) { setTimeout(processing, 5); } } processing();
Я бы рассмотрел преобразование 3500 элементов из xml на сервер JSON или даже лучше загрузить его на сервер, преобразованный, так что он является родным для JS из getgo.
это минимизирует вашу нагрузку и, возможно, уменьшит размер файла.
длинные петли без замораживания браузера возможно с Turboid framework. С его помощью вы можете написать код:
loop(function(){ // Do something... }, number_of_iterations, number_of_milliseconds);подробнее в этом turboid.net статья:реальные циклы в Javascript
Javascript является однопоточным, поэтому помимо
setTimeout, там не много вы можете сделать. Если использование Google Gears является опцией для вашего сайта, они предоставляют возможность запускать javascript в истинном фоновом потоке.
вы можете использовать HTML5 workers API, но это будет работать только на Firefox 3.1 и Safari 4 betas atm.
у меня была та же проблема, которая происходит, когда пользователь обновил страницу подряд. Причиной были два вложенных цикла, которые произошли более 52000 раз. Эта проблема была более жесткой в Firefox 24, чем в Chrome 29, так как Firefox рухнет раньше (около 2000 МС раньше, чем Chrome). То, что я просто сделал, и это сработало, состояло в том, что я использовал циклы "для" Вместо каждого, а затем я рефакторировал код так, чтобы я разделил весь массив циклов на 4 отдельных вызова, а затем объединил результат в один. Это решение доказало, что оно работает.
что-то вроде этого:
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в разных рабочих и объединить результат в главном звонивший рабочий.Я имею в виду, что это может быть не идеально, но это сработало. Я могу помочь с более реальными фрагментами кода, если кто-то все еще думает, что это может их удовлетворить.
вы можете попробовать сократить код на
$(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));Это не повредит вам много ;)
как модификация @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();
Comments