我能否在JavaScript中转义HTML特殊字符?

360

我想通过JavaScript函数将文本显示到HTML上。在JavaScript中如何转义HTML特殊字符?是否有相应的API可以使用?


19
这不是一篇重复的问题,因为这个问题与jQuery无关。我只对这一个感兴趣,因为我不使用jQuery... - lvella
6
可能是[HtmlSpecialChars equivalent in Javascript?](https://dev59.com/SnI-5IYBdhLWcg3wkJQA)的重复问题。 - Bergi
请注意,浏览器正在使用新的HTML Sanitizer API(https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API)进行操作。 - Flimm
17个回答

525

以下是一种几乎适用于所有Web浏览器的解决方案:

function escapeHtml(unsafe)
{
    return unsafe
         .replace(/&/g, "&")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;")
         .replace(/"/g, "&quot;")
         .replace(/'/g, "&#039;");
 }

如果您仅支持现代网络浏览器(2020+),则可以使用新的replaceAll函数:

const escapeHtml = (unsafe) => {
    return unsafe.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;');
}

19
为什么要用“'”而不是“'”? - sereda
57
因为:https://dev59.com/53I95IYBdhLWcg3w8iv1 为什么不应该使用'来转义单引号? - Shreyans
3
我认为在 replace() 函数中使用正则表达式是不必要的。普通的单个字符字符串同样可以胜任。 - jamix
38
有标准API吗,还是只能用这种方式? - Sunil Garg
4
'在HTML5中是有效的,但在HTML4中不是。 - Hank W
显示剩余9条评论

77

function escapeHtml(html){
  var text = document.createTextNode(html);
  var p = document.createElement('p');
  p.appendChild(text);
  return p.innerHTML;
}

// Escape while typing & print result
document.querySelector('input').addEventListener('input', e => {
  console.clear();
  console.log( escapeHtml(e.target.value) );
});
<input style='width:90%; padding:6px;' placeholder='&lt;b&gt;cool&lt;/b&gt;'>


在这里工作但是在浏览器离线状态下不起作用。 - user8850199
10
请注意,这个函数不能转义引号("'),因此如果在HTML标记属性中使用该函数生成的字符串,仍然可能会造成损害。 - jdgregson

61

使用Lodash

_.escape('fred, barney, & pebbles');
// => 'fred, barney, &amp; pebbles'

源代码


这是什么的反义词?有哪个函数是它的反义函数? - Sunil Garg
6
同样的功能在underscore中:https://underscorejs.org/#escape和https://underscorejs.org/#unescape - juanmirocks
当您尝试_.escape(192.168.1.1)时,似乎无法用于IP地址,但如果我添加引号,那么它就可以工作:_.escape('52.60.62.147'),即使我引用的变量的值不是字符串。LoDash太棒了! - Kreidol

60
你可以使用 jQuery 的 .text() 函数
例如:

http://jsfiddle.net/9H6Ch/

从jQuery文档关于.text()函数的说明中得知:
我们需要知道,这个方法会适当地转义提供的字符串,使其在HTML中正确渲染。为此,它调用DOM方法.createTextNode(),而不解释该字符串为HTML。
jQuery文档的早期版本是这样表述的(强调添加):
我们需要知道,这个方法会适当地转义提供的字符串,以便在HTML中正确渲染。为此,它调用DOM方法.createTextNode(),它将特殊字符替换为它们的HTML实体等效项(如&amplt替换<)。

10
如果您只想进行转换,甚至可以在新元素上使用它,例如:const str = "foo<>'\"&"; 使用 $('<div>').text(str).html() 将得到 foo&lt;&gt;'"&amp; - amoebe
4
请注意,这将使引号 '" 未转义,这可能会让您感到困惑。 - Ben Philipp

47

到目前为止,这是我看过的最快的方法。此外,它在不添加、删除或更改页面元素的情况下完成了所有操作。

function escapeHTML(unsafeText) {
    let div = document.createElement('div');
    div.innerText = unsafeText;
    return div.innerHTML;
}

18
警告:它并不处理引号,因此您不能在HTML代码的属性值中使用输出结果。例如,var divCode = '<div data-title="' + escapeHTML('Jerry "Bull" Winston') + '">Div content</div>' 将生成无效的HTML! - izogfif
1
使用div.textContent而不是div.innerText可能更符合惯用语。 - Klesun
我在想,反复调用这个函数会不会最终使文档充满了额外的div元素?还是说它会被垃圾回收机制清理掉? - Magnus
@Magnus div元素没有附加到DOM上,因此它最终将被垃圾回收。所以不会向文档中添加无用的元素。 - Michael T

44

我认为我找到了正确的方法来做这件事...

// Create a DOM Text node:
var text_node = document.createTextNode(unescaped_text);

// Get the HTML element where you want to insert the text into:
var elem = document.getElementById('msg_span');

// Optional: clear its old contents
//elem.innerHTML = '';

// Append the text node into it:
elem.appendChild(text_node);

今天我学到了有关HTML的新知识。https://www.w3schools.com/jsref/met_document_createtextnode.asp。 - Sellorio
4
请注意,如果您像这样访问文本节点 document.createTextNode("<script>alert('Attack!')</script>").textContent,则文本节点的内容不会被转义。请斟酌使用。 - maechler
如果你只是设置文本,那么这是正确的方法。那也是textContent,但显然它的支持不太好。但是,如果你正在构建一个包含一些文本和一些HTML的字符串,那么这种方法就行不通了,你仍然需要进行转义。 - jgmjgm
我真的很喜欢这个,因为它正确地使用了DOM。它感觉比大多数其他选项都不那么“hacky”。 - TRiG

25

找到一个更好的解决方案很有趣:

var escapeHTML = function(unsafe) {
  return unsafe.replace(/[&<"']/g, function(m) {
    switch (m) {
      case '&':
        return '&amp;';
      case '<':
        return '&lt;';
      case '"':
        return '&quot;';
      default:
        return '&#039;';
    }
  });
};

我不解析>,因为它不会破坏结果中的XML / HTML代码。

以下是基准测试:http://jsperf.com/regexpairs 此外,我创建了一个通用的escape函数:http://jsperf.com/regexpairs2


1
很有趣的是,使用switch比map快得多。我没想到!谢谢分享! - Peter T.
1
有许多许多Unicode字符,您可能编码和考虑的比数量还要多。我根本不建议使用这种手动方法。 - vsync
为什么你要转义多字节字符呢?直接在所有地方使用UTF-8就好了。 - Neonit
6
跳过 > 可能会破坏代码。请记住,<> 内也是 HTML。在这种情况下,跳过 > 将会导致破裂。如果你只是为标签之间转义,那么你可能只需要转义 < 和 &。 - jgmjgm

14

显示未编码文本的最简洁和高效方法是使用textContent属性。

比使用innerHTML更快。而且这还没有考虑转义开销。

document.body.textContent = 'a <b> c </b>';


@ZzZombo,如果你在style和script标签中添加内容,它无法正常工作是完全正常的。因为你添加的是代码而不是文本,所以在这种情况下应该使用innerHTML。此外,你不需要转义它们,因为这两个特殊标签不会被解析为HTML。在解析时,它们的内容被视为文本,直到遇到结束序列</ - user

10
按照规定办事
在编辑HTML属性时,请使用推荐的“HTML属性编码”:
OWASP建议“除了字母数字字符外,应该使用ASCII值小于256的所有字符进行转义,格式为&#xHH;(如果有命名实体可用,则使用命名实体),以防止切换到属性之外。”
因此,这里有一个执行此操作的函数,并附带一个使用示例:

function escapeHTML(unsafe) {
  return unsafe.replace(
    /[\u0000-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u00FF]/g,
    c => '&#' + ('000' + c.charCodeAt(0)).slice(-4) + ';'
  )
}

document.querySelector('div').innerHTML =
  '<span class=' +
  escapeHTML('"fakeclass" onclick="alert("test")') +
  '>' +
  escapeHTML('<script>alert("inspect the attributes")\u003C/script>') +
  '</span>'
<div></div>

你应该验证我提供的实体范围,以确保函数的安全性。你也可以使用这个正则表达式,它具有更好的可读性,并且应该涵盖相同的字符代码,但在我的浏览器中性能要低大约10%:
/(?![0-9A-Za-z])[\u0000-\u00FF]/g

在编辑<tags>之间的HTML内容时,请使用"HTML实体编码":

对于这一点,OWASP建议你"查看.textContent属性,因为它是一个安全的接收端,会自动进行HTML实体编码。"

8

DOM元素支持通过将文本分配给 innerText 将文本转换为HTML。 innerText不是一个函数,但是像分配文本被转义一样工作。

document.querySelectorAll('#id')[0].innerText = 'unsafe " String >><>';

1
至少在Chrome中,分配多行文本会在换行符的位置添加<br>元素,这可能会破坏某些元素,如样式或脚本。 createTextNode不容易出现这个问题。 - ZzZombo
1
innerText存在一些遗留/规范问题。最好使用textContent - Roy Tinker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接