Чем заменить весь секрет производительности? [HTML escape]

Я потратил некоторое время на поиски лучшего способа избежать html-строки и нашел несколько дискуссий на эту тему: обсуждение 1 обсуждение 2. Это приводит меня к замене всех функций. Затем я провел тесты производительности и попытался найти решение, обеспечивающее аналогичную скорость, но безуспешно:(

Вот мой последний набор тестов. Я нашел его в сети и расширил своими попытками (4 случая внизу) и до сих пор не могу достучаться replaceAll() спектакль.

Что делает тайная ведьма replaceAll() Решение так быстро?

Здоровается!

Фрагменты кода:

String.prototype.replaceAll = function(str1, str2, ignore) 
{
   return this.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignore?"gi":"g")),(typeof(str2)=="string")?str2.replace(/\$/g,"$$$$"):str2);
};

кредиты для QWERTY

Самый быстрый случай на данный момент:

html.replaceAll('&', '&amp;').replaceAll('"', '&quot;').replaceAll("'", '&#39;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');

3 ответа

Решение

Наконец я нашел это! Спасибо, Ja͢ck, за то, что указал мне на jsperf

Должен отметить, что результаты теста странные; Когда.replaceAll() определен внутри Benchmark.prototype.setup, он выполняется в два раза быстрее по сравнению с глобальным определением (т. е. внутри тега). Я до сих пор не уверен, почему это так, но это определенно должно быть связано с тем, как работает сам jsperf.

Ответ:

replaceAll - это достижение лимита / ошибки jsperf, вызванное специальной последовательностью "\\$&", так что результаты были неправильными.

compile() - когда вызывается без аргумента, он меняет определение регулярного выражения на /(?:), Я не знаю, если это ошибка или что-то, но результат производительности был дрянной после того, как он был вызван.

Вот мой результат безопасных испытаний.

Наконец я подготовил надлежащие тесты.

В результате для HTML лучше всего использовать нативное решение на основе DOM, например:

document.createElement('div').appendChild(document.createTextNode(html)).parentNode.innerHTML

или если вы повторяете это много раз, вы можете сделать это с подготовленными переменными:

//prepare variables
var DOMtext = document.createTextNode("test");
var DOMnative = document.createElement("span");
DOMnative.appendChild(DOMtext);

//main work for each case
function HTMLescape(html){
  DOMtext.nodeValue = html;
  return DOMnative.innerHTML
}

Спасибо всем за сотрудничество и размещение комментариев и указаний.

описание ошибки jsperf

String.prototype.replaceAll был определен следующим образом:

function (str1, str2, ignore) {
  return this.replace(new RegExp(str1.replace(repAll, "\\#{setup}"), (ignore ? "gi" : "g")), (typeof(str2) == "string") ? str2.replace(/\$/g, "$$") : str2);
}

Что касается производительности, я обнаружил, что приведенная ниже функция хороша:

String.prototype.htmlEscape = function() {
    var amp_re = /&/g, sq_re = /'/g, quot_re = /"/g, lt_re = /</g, gt_re = />/g;

    return function() {
        return this
          .replace(amp_re, '&amp;')
          .replace(sq_re, '&#39;')
          .replace(quot_re, '&quot;')
          .replace(lt_re, '&lt;')
          .replace(gt_re, '&gt;');
    }
}();

Он инициализирует регулярные выражения и возвращает замыкание, которое фактически выполняет замену.

Тест производительности

Должен отметить, что результаты теста странные; когда .replaceAll() определяется внутри Benchmark.prototype.setup он работает в два раза быстрее по сравнению с глобальным определением (т. е. внутри <script> тег). Я до сих пор не уверен, почему это так, но это определенно должно быть связано с тем, как работает сам jsperf.

С помощьюRegExp.compile()

Я хотел избежать использования устаревшей функции, главным образом потому, что такой вид производительности должен выполняться автоматически в современных браузерах. Вот версия с скомпилированными выражениями:

String.prototype.htmlEscape2 = function() {
    var amp_re = /&/g, sq_re = /'/g, quot_re = /"/g, lt_re = /</g, gt_re = />/g;

    if (RegExp.prototype.compile) {
        amp_re.compile();
        sq_re.compile();
        quot_re.compile();
        lt_re.compile();
        gt_re.compile();
    }

    return function() {
        return this
          .replace(amp_re, '&amp;')
          .replace(sq_re, '&#39;')
          .replace(quot_re, '&quot;')
          .replace(lt_re, '&lt;')
          .replace(gt_re, '&gt;');
    }
}

Все это выдувает все остальное из воды!

Тест производительности

Причина по которой .compile() дает такой прирост производительности, потому что, когда вы компилируете глобальное выражение, например, /a/g это превращается в /(?:)/ (на Chrome), что делает его бесполезным.

Если компиляция не может быть выполнена, браузер должен выдать ошибку, а не молча уничтожать ее.

На самом деле есть более быстрые способы сделать это.

Если бы вы могли выполнить встроенное разделение и присоединение, вы получите лучшую производительность.

//example below
var test = "This is a test string";
var test2 = test.split("a").join("A");

Попробуйте это и запустите тест производительности.

Другие вопросы по тегам