Статьи

Перевод: Николай Мациевский aka sunnybear
Опубликована: 22 мая 2008

Поиск без замены, или массивы без массивов

Примечание: ниже находится перевод заметки "Search and Don't Replace". В ней автор размышляет о методах преобразования строки запроса в массив на JavaScript при минимальных затратах процессорного времени. Мои комментарии далее курсивом.

Немного ранее сегодня мой друг, Marc Grabanski, подкинул мне вопрос: как наиболее оптимальным образом на JavaScript преобразовать строку запроса вида foo=1&foo=2&foo=3&blah=a&blah=b во что-то вроде foo=1,2,3&blah=a,b? У него уже было на тот момент собственное решение, и ему было любопытно, нельзя ли его как-либо улучшить.

Я подумал немного и предложил следующее решение:

function compress(data){
    var q = {}, ret = "";
    data.replace(/([^=&]+)=([^&]*)/g,     function(m, key, value){
        q[key] = (q[key] ? q[key] + "," : "") + value;
    });
    for ( var key in q )
        ret = (ret ? ret + "&" : "") + key + "=" + q[key];
    return ret;
}

Помимо того, что оно на 10 строчек короче, чем решение Mark, данный пример также не использует никаких библиотек (Mark использует jQuery). Мой друг был весьма удивлен моим результатом и особенно той техникой замены, которую я применил. Я бы хотел выделить пару интересных приемов, которые я использовал и которые могут быть незнакомы большинству веб-программистов.

Создаем массив без использования массива

Первым шагом на пути решения данной проблемы было объединение нескольких различных значений, привязанных к одному ключу. Объединение происходило при помощи ','. На первый взгляд, это очевидное решение просто создает массив для каждого ключа, добавляя новые значения в конец и затем собирая массив в одну строку при помощи .join(). Однако, все эти действия как достаточно трудоемки (и требуют создания отдельного массива), так и занимают большой объем кода.

В качестве альтернативы я дважды использовал в программе следующий подход:

q[key] = (q[key] ? q[key] + "," : "") + value;

Это ключевой момент в понимании того, как значения value объединяются в одну строку q[key] только при помощи добавочного ",", если в этой строке еще ничего нет: q[key] ? q[key] + "," : "". Если для этого ключа еще нет ни одного значения, мы объединяем текущее значение с пустой строкой, в противном случае мы просто объединяем текущее значение с существующим значением, используя ','.

Стоит заметить, что мы должны экранировать в данном случае дополнительный символ: ','. Однако, как было справедливо замечено, таким образом мы полностью эмулируем создание массива, но несколько меньшей кровью. Ведь если заключить получившуюся строку в квадратные кавычки и сделать на нее eval, мы, действительно, получим массив (речь идет о переменной q[key], которая является, в свою очередь, также элементом массива, т.е. вместо двумерного массива значений мы используем одномерный массив строк).

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

Ищем и заменяем без использования замены

Второй и, вероятно, более интересный аспект данного примера заключается в использовании строковой функции JavaScript replace как способа для перебора значений в строке, а не для обычного механизма найти-и-заменить. Это двойной прием: мы передаем функцию как аргумент, на который нужно заменять найденное выражение, в методе .replace() и, вместо возврата значения (которое должно заменить найденное), мы используем функцию для поиска.

Давайте рассмотрим следующий участок кода:

data.replace(/([^=&]+)=([^&]*)/g, function(m, key, value){});

Регулярное выражение, само по себе, выделяет 2 вещи — ключ в строке запроса и связанное с этим ключом значение. И этот поиск пар ключ-значение происходит глобально по всей строке запроса (в данном случае имеется в виду, что на не нужно дополнительно перебирать весь массив — глобальный метод replace обеспечивает такой функционал)).

Вторым аргументом в методе replace является функция. Вообще говоря, не так часто применяют такую технику функция-как-аргумент при доступе к найденным сложным значениям в replace (т.е. значения, вообще говоря, зависят от найденных участков строки). Функция вызывается каждый раз, когда в строке обнаруживается новое совпадение, при этом функция получит переменное число аргументов. Первым аргументом всегда является весь участок строки, который попадает под регулярное выражение, все оставшиеся аргументы будут являться значениями, по порядку совпадающими с блоками (...). Возвращаемое функцией значение вставляется обратно в строку как замена найденной подстроке. В данном примере мы ничего не возвращаем, поэтому каждый раз мы вставляем "undefined" в исходную строку.

Проверить это поведение можно следующим образом:

"a b c".replace(/a/, function(){});
    // => "undefined b c"

Теперь, когда мы собрали все наши пары ключ-значение (to be re-serialized back into a query string), финальным шаг будет и не шагом вовсе: мы просто не будем сохранять всю нашу строку данных, полученную в ходе операции найти-и-заменить (которая, по всей вероятности, выглядит примерно как "undefined&undefined&undefined").

Таким образом, мы использовали метод замены строки как нашу собственную специфическую поисковую функцию. Результат оказался не только быстрым, но также простым и эффективным. И это будет еще одним замечательным примером в вашу копилку использования JavaScript.

Читать дальше

Все комментарии (habrahabr.ru)