【问题标题】:Fastest method to escape HTML tags as HTML entities?将 HTML 标签转义为 HTML 实体的最快方法?
【发布时间】:2011-07-26 19:48:26
【问题描述】:

我正在编写一个 Chrome 扩展程序,其中涉及执行 很多 以下工作:通过转换 <、@987654322 来清理 可能 包含 HTML 标记的字符串@ 和& 分别为<>&

(换句话说,与 PHP 的 htmlspecialchars(str, ENT_NOQUOTES) 相同——我认为没有任何真正需要转换双引号字符。)

这是迄今为止我发现的最快的函数:

function safe_tags(str) {
    return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') ;
}

但是当我必须一次运行几千个字符串时仍然存在很大的延迟。

有人可以改进吗?它主要用于 10 到 150 个字符之间的字符串,如果这有所不同的话。

(我的一个想法是不要费心编码大于号——这样做会有什么真正的危险吗?)

【问题讨论】:

  • 为什么?在大多数情况下,您想要这样做,您希望将数据插入 DOM,在这种情况下,您应该忘记转义它,只需从中创建一个 textNode。
  • @David Dorward:也许他想清理 POST 数据,而服务器没有正确地往返数据。
  • @Lie — 如果是这样,那么解决方案是“看在 Pete 的份上,修复服务器,因为你有一个很大的 XSS 漏洞”
  • @David Dorward:有可能是他无法控制服务器。我最近遇到了这样的情况,我正在编写一个greasemonkey 脚本来解决我在大学网站上不喜欢的一些事情;我必须在我无法控制的服务器上进行 POST 并使用 javascript 清理 POST 数据(因为原始数据来自富文本框,因此有大量 html 标签在服务器上不进行往返) .网络管理员无视我要求他们修复网站的请求,所以我别无选择。
  • 我有一个用例,我需要在 div 中显示错误消息。错误消息可以包含 HTML 和换行符。我想转义 HTML 并用
    替换换行符。然后把结果放到一个div中展示。

标签: javascript html regex performance string


【解决方案1】:

我会将XMLSerializer 添加到堆栈中。它在不使用任何对象缓存的情况下提供了最快的结果(不是在序列化程序上,也不是在 Text 节点上)。

function serializeTextNode(text) {
  return new XMLSerializer().serializeToString(document.createTextNode(text));
}

额外的好处是它支持与文本节点不同的序列化属性:

function serializeAttributeValue(value) {
  const attr = document.createAttribute('a');
  attr.value = value;
  return new XMLSerializer().serializeToString(attr);
}

您可以通过查看 text nodesattribute values 的规范来查看它实际替换的内容。完整的文档有更多的节点类型,但概念是一样的。

至于性能,不缓存的时候是最快的。当您确实允许缓存时,在带有子 Text 节点的 HTMLElement 上调用 innerHTML 是最快的。正则表达式将是最慢的(正如其他 cmets 所证明的那样)。当然,XMLSerializer 在其他浏览器上可能更快,但在我的(有限)测试中,innerHTML 最快。


最快的单行:

new XMLSerializer().serializeToString(document.createTextNode(text));

缓存速度最快:

const cachedElementParent = document.createElement('div');
const cachedChildTextNode = document.createTextNode('');
cachedElementParent.appendChild(cachedChildTextNode);

function serializeTextNode(text) {
  cachedChildTextNode.nodeValue = text;
  return cachedElementParent.innerHTML;
}

https://jsperf.com/htmlentityencode/1

【讨论】:

    【解决方案2】:

    更快/更短的解决方案是:

    escaped = new Option(html).innerHTML
    

    这与 JavaScript 的一些奇怪痕迹有关,其中 Option 元素保留了一个自动执行这种转义的构造函数。

    感谢https://github.com/jasonmoo/t.js/blob/master/t.js

    【讨论】:

    • 整洁的单行,但正则表达式之后的slowest method。此外,根据spec,此处的文本可以删除空格
    • 请注意,@ShortFuse 的“最慢方法”链接使我的系统内存不足(大约 6GB 可用),并且 firefox 似乎在内存不足之前停止分配,而不是杀死有问题的进程, linux 会坐在那里,让你硬关机。
    【解决方案3】:

    我不完全确定速度,但如果您希望简单,我建议您使用 lodash/underscore escape 函数。

    【讨论】:

      【解决方案4】:

      节目有点晚了,但使用encodeURIComponent()decodeURIComponent() 有什么问题?

      【讨论】:

      • 那些做完全不相关的事情
      • 也许是我听过的对“完全”这个词最大的滥用。例如,关于主题问题,它可以用于解码一个 html 字符串(显然是出于某种存储原因),而不考虑 html 标签,然后在需要时轻松地将其重新编码回 html。跨度>
      • @callum 是正确的:问题是关于 html 实体,而你回答的是 uri 组件,它们完全不同的。
      【解决方案5】:

      一体化脚本:

      // HTML entities Encode/Decode
      
      function htmlspecialchars(str) {
          var map = {
              "&": "&amp;",
              "<": "&lt;",
              ">": "&gt;",
              "\"": "&quot;",
              "'": "&#39;" // ' -> &apos; for XML only
          };
          return str.replace(/[&<>"']/g, function(m) { return map[m]; });
      }
      function htmlspecialchars_decode(str) {
          var map = {
              "&amp;": "&",
              "&lt;": "<",
              "&gt;": ">",
              "&quot;": "\"",
              "&#39;": "'"
          };
          return str.replace(/(&amp;|&lt;|&gt;|&quot;|&#39;)/g, function(m) { return map[m]; });
      }
      function htmlentities(str) {
          var textarea = document.createElement("textarea");
          textarea.innerHTML = str;
          return textarea.innerHTML;
      }
      function htmlentities_decode(str) {
          var textarea = document.createElement("textarea");
          textarea.innerHTML = str;
          return textarea.value;
      }
      

      http://pastebin.com/JGCVs0Ts

      【讨论】:

      • 我没有投反对票,但所有正则表达式样式的替换都无法编码​​ unicode...所以,任何使用外语的人都会感到失望。上面提到的
      • 正则表达式对我来说适用于许多非拉丁 Unicode 字符。我不会期待别的。你怎么觉得这行不通?您是否正在考虑需要 HTML 实体的单字节代码页?这就是第三个和第四个功能的用途,而不是第一和第二个。我喜欢这种差异化。
      • @LonelyPixel 如果您不提及他,我认为他不会看到您的评论(“只能通知另外一位用户;将始终通知帖子所有者”)
      • 我根本不知道有针对性的通知存在。 @Ajax 请看我上面的评论。
      • @LonelyPixel 我现在看到了。出于某种原因,我认为此答案中没有 textarea 样式替换。我确实在考虑双代码点大 unicode 值,比如普通话。我的意思是,可以使正则表达式足够智能,但是当您查看浏览器供应商可以采用的快捷方式时,我认为 textarea 会更快(比完全胜任的正则表达式)要快得多。有人在这个答案上发布了基准吗?我发誓我见过一个。
      【解决方案6】:

      function encode(r) {
        return r.replace(/[\x26\x0A\x3c\x3e\x22\x27]/g, function(r) {
      	return "&#" + r.charCodeAt(0) + ";";
        });
      }
      
      test.value=encode('How to encode\nonly html tags &<>\'" nice & fast!');
      
      /*
       \x26 is &ampersand (it has to be first),
       \x0A is newline,
       \x22 is ",
       \x27 is ',
       \x3c is <,
       \x3e is >
      */
      &lt;textarea id=test rows=11 cols=55&gt;www.WHAK.com&lt;/textarea&gt;

      【讨论】:

        【解决方案7】:

        最快的方法是:

        function escapeHTML(html) {
            return document.createElement('div').appendChild(document.createTextNode(html)).parentNode.innerHTML;
        }
        

        这种方法比基于 'replace' 的方法快大约两倍,参见http://jsperf.com/htmlencoderegex/35

        来源:https://stackoverflow.com/a/17546215/698168

        【讨论】:

          【解决方案8】:

          AngularJS 源代码在angular-sanitize.js 中也有一个版本。

          var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
              // Match everything outside of normal chars and " (quote character)
              NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
          /**
           * Escapes all potentially dangerous characters, so that the
           * resulting string can be safely inserted into attribute or
           * element text.
           * @param value
           * @returns {string} escaped text
           */
          function encodeEntities(value) {
            return value.
              replace(/&/g, '&amp;').
              replace(SURROGATE_PAIR_REGEXP, function(value) {
                var hi = value.charCodeAt(0);
                var low = value.charCodeAt(1);
                return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
              }).
              replace(NON_ALPHANUMERIC_REGEXP, function(value) {
                return '&#' + value.charCodeAt(0) + ';';
              }).
              replace(/</g, '&lt;').
              replace(/>/g, '&gt;');
          }
          

          【讨论】:

          • 哇,非字母正则表达式很激烈。我不认为|不过在表达式中是需要的。
          【解决方案9】:

          这是您可以做到这一点的一种方法:

          var escape = document.createElement('textarea');
          function escapeHTML(html) {
              escape.textContent = html;
              return escape.innerHTML;
          }
          
          function unescapeHTML(html) {
              escape.innerHTML = html;
              return escape.textContent;
          }
          

          Here's a demo.

          【讨论】:

          • 重新设计了演示。这是全屏版本:jsfiddle.net/Daniel_Hug/qPUEX/show/light
          • 不确定如何/什么/为什么 - 但这是天才。
          • 看起来它正在利用 TextArea 元素的现有代码来转义文字文本。太好了,我想这个小技巧会找到另一个家。
          • @jazkat 我没有使用那个功能。我使用的转义变量,我在示例中定义自己。
          • 但这会丢失空格等吗?
          【解决方案10】:

          Martijn 的方法作为单个函数处理 " 标记(在 javascript 中使用):

          function escapeHTML(html) {
              var fn=function(tag) {
                  var charsToReplace = {
                      '&': '&amp;',
                      '<': '&lt;',
                      '>': '&gt;',
                      '"': '&#34;'
                  };
                  return charsToReplace[tag] || tag;
              }
              return html.replace(/[&<>"]/g, fn);
          }
          

          【讨论】:

          【解决方案11】:

          Martijn 的方法作为原型函数:

          String.prototype.escape = function() {
              var tagsToReplace = {
                  '&': '&amp;',
                  '<': '&lt;',
                  '>': '&gt;'
              };
              return this.replace(/[&<>]/g, function(tag) {
                  return tagsToReplace[tag] || tag;
              });
          };
          
          var a = "<abc>";
          var b = a.escape(); // "&lt;abc&gt;"
          

          【讨论】:

          • 像这样添加到String 它应该是 escapeHtml 因为它不是一般的字符串转义。那就是String.escapeHtml 是正确的,但是String.escape 提出了一个问题,“逃避什么?”
          • 是的,好主意。这些天我已经不再扩展原型以避免冲突。
          • 如果您的浏览器支持 Symbol,您可以使用它来避免污染字符串键命名空间。 var escape = new Symbol("escape"); String.prototype[escape] = function(){ ... }; "文本"[转义]();
          • 加一为例子。
          【解决方案12】:

          您可以尝试传递一个回调函数来执行替换:

          var tagsToReplace = {
              '&': '&amp;',
              '<': '&lt;',
              '>': '&gt;'
          };
          
          function replaceTag(tag) {
              return tagsToReplace[tag] || tag;
          }
          
          function safe_tags_replace(str) {
              return str.replace(/[&<>]/g, replaceTag);
          }
          

          这是一个性能测试:http://jsperf.com/encode-html-entities 与重复调用replace 函数进行比较,并使用 Dmitrij 提出的 DOM 方法。

          你的方式似乎更快...

          你为什么需要它呢?

          【讨论】:

          • 不用转义&gt;
          • 其实如果你把转义值放在一个html元素的属性中,你需要转义>符号。否则它会破坏该 html 元素的标签。
          • 在普通文本中,转义字符很少见。如果您关心最大速度,最好只在需要时调用替换:if (/[&lt;&gt;&amp;"]/.test(str) { ... }
          • @callum: 不。我对列举我认为“可能出错”的案例不感兴趣(尤其是因为意外/遗忘的案例会伤害你,而且你最不喜欢期待它)。我对按照标准进行编码很感兴趣(因此,意外/被遗忘的情况不会根据定义伤害您)。我不能强调这是多么重要。 &gt; 是 HTML 中的特殊字符,因此请转义它。就那么简单。 :)
          • @LightnessRacesinOrbit 这很重要,因为问题是什么是最快的方法。如果可以跳过 &gt; 替换,那会更快。
          猜你喜欢
          • 1970-01-01
          • 2018-04-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-08-31
          • 1970-01-01
          • 2012-04-21
          • 1970-01-01
          相关资源
          最近更新 更多