ECMA- / Javascripts Array.prototype.forEach - PullRequest
       4

ECMA- / Javascripts Array.prototype.forEach

4 голосов
/ 19 октября 2010

Javascript (ECMAscript) поддерживает метод Array.prototype.forEach начиная с версии 1.6 (ECMAscript edition 3, 2005). Так что многие браузеры уже поддерживают этот метод, и он невероятно быстр по сравнению, например, с методом jQuery $.each().
(На самом деле он превосходит все реализации, независимо от того, какая библиотека Javascript)

По сравнению с jQuery это примерно на 60-70% быстрее. Попробуйте сами на forEach против jQuery на JSPerf.

Единственный недостаток, который я до сих пор использовал, это то, что я не могу придумать способ прервать итерацию раньше. Как for, while и do-while имеют оператор break, jQuerys .each() поддерживает return false для прерывания цикла.

Я изучил спецификации ECMAScript Edition 5 (последнее, что я нашел), но в них не упоминается ранний разрыв из этого цикла.

Итак, вопрос, будет ли мистер Дуглас Крокфорд называть это design error?
Я что-то упустил, и возможно ли разорвать такой цикл раньше?

Редактировать

Спасибо за ответы. Я полагаю, поскольку никто не придумал «нативное» решение, для этого действительно нет реализации (возможно, особенность?) Во всяком случае, мне не очень нравятся предложенные методы, поэтому я немного обманулся и наконец нашел тот, который мне нравится (по крайней мере, лучше). Похоже:

var div     = document.createElement('div'),
divStyle    = div.style,
support     = jQuery.support,
arr         = ['MozTransform', 'WebkitTransform', 'OTransform'];

arr.slice(0).forEach(function(v,i,a){    
    if(divStyle[v] === ''){
       support.transform = v;
       a.length = 0;
    }
});

Это "настоящий" производственный код. Я искал хороший способ поиска свойств преобразования css3, и я заблудился в спецификациях ecma5 и странных форумах Javascript: -)

Таким образом, вы можете передать сам объект массива в качестве третьего параметра в обратный вызов .forEach. Я создаю копию из исходного массива с вызовом slice(0) и устанавливаю для его свойства .length значение 0, как только я нашел то, что искал. Работает довольно хорошо.

Если кто-то найдет лучшее решение, я, конечно, отредактирую его.

Ответы [ 4 ]

7 голосов
/ 20 октября 2010

Ну, вместо этого вы можете использовать every.Функция предназначена для возврата true, если предоставленная функция обратного вызова возвращает true (truey) для каждого элемента в массиве и false в противном случае.Ключевым моментом здесь является то, что, как только функция обратного вызова возвращает ложное значение, every немедленно возвращает false без итерации по остальной части массива.Поэтому просто проигнорируйте возвращаемое значение и обработайте every, как если бы оно было forEach.

[1, 2, 3, 4, 5].every(function(e) {
   alert(e);

   if (e > 2) return false; //break

   return true;
});

В этом примере alert сработает только 3 раза.

Конечнобольшая проблема в том, что вы должны вернуть истинное значение, если хотите, чтобы цикл продолжался.Фактически, если вы ничего не вернете (не определено), цикл остановится.В качестве альтернативы вы можете использовать some, который ведет себя совершенно противоположным образом;немедленно возвращает истину, когда функция обратного вызова возвращает истинное значение, и ложь, если это никогда не происходит.В этом случае return true будет вашим оператором "break".В обратном направлении да, но это избавляет вас от необходимости return true просто продолжать цикл.

Теперь, если вы ограничены производительностью, я бы порекомендовал просто использовать обычный цикл for, так какЭто значительный объем накладных расходов, возникающих при вызове функций, которые очень быстро складываются с большими массивами.Использование собственных функций несколько помогает, но накладные расходы при повторных вызовах пользовательских функций все еще существуют и нетривиальны.По крайней мере, таков мой опыт, вы, конечно, должны провести собственное тестирование.

1 голос
/ 20 октября 2010

Вы можете выдать ошибку, чтобы выйти из итерации:

if (typeof BreakIteration == "undefined") {
  BreakIteration = new Error("BreakIteration");
}

try {
    [0,1,2,3,4].forEach(function(key, value) {            
        console.log(key + ': ' + value);

        if (value == 3) {
            throw BreakIteration;
        }
    });
} catch (error) {
    if (error !== BreakIteration) {
        throw error;
    }
}

Это не совсем красиво.Я согласен - ошибка спецификации.

1 голос
/ 19 октября 2010

Я бы предположил, что у вас есть кусок кода, который нужно запустить для каждого отдельного элемента. Не используйте forEach, чтобы найти элемент или аналогично операции «останов в середине». Примените эту функцию к каждому элементу в массиве.

Вы можете (я думаю) использовать какой-то глобальный индикатор для проверки и возврата false, если "найдено" или что-то в этом роде.

var found = false;
array.forEach(function (k, v) { 
    if (found) 
    { 
        return false; 
    } 
    else 
    { 
        if (v == 1) 
        { 
            found = true; 
        }
    } 
}); 
0 голосов
/ 20 октября 2010

Я думаю, что использовал бы другую конструкцию, если я хочу выполнить цикл для поиска.

Возможность вырваться из forEach-ing - это, конечно, throw что-то, хотя это не совсем тот способ, которым хотелось бы использовать throw ...

...