前端开发工作中,经常需要将 HTML 的左右尖括号等转义成实体形式。我们不能把<,>,&等直接显示在最终看到的网页里。需要将其转义后才能在网页上显示。不转义直接将用户输入写到网页上往往是行不通的,很容易出现 XSS 漏洞。
转义字符(Escape Sequence)也称字符实体(Character Entity)。定义转义字符串的主要原因是“<”和“>”等符号已经用来表示 HTML TAG,因此不能直接当作文本中的符号来使用。但有时需求是在 HTML 页面上使用这些符号,所以需要定义它的转义字符串。
var entityMap = {
escape: {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
unescape: {
'&': "&",
''': "'",
'>': ">",
'<': "<",
'"': '"',
var entityReg = {
escape: RegExp('[' + Object.keys(entityMap.escape).join('') + ']', 'g'),
unescape: RegExp('(' + Object.keys(entityMap.unescape).join('|') + ')', 'g')
// 将HTML转义为实体
function escape(html) {
if (typeof html !== 'string') return ''
return html.replace(entityReg.escape, function(match) {
return entityMap.escape[match]
// 将实体转回为HTML
function unescape(str) {
if (typeof str !== 'string') return ''
return str.replace(entityReg.unescape, function(match) {
return entityMap.unescape[match]
这种方法灵活性,完整性都比较好,可根据需求添加或减少映射表,且可以运行在任意 JS 环境中。
浏览器 DOM API 实现转义
我们先简单介绍一下接下来需要使用的几个 api,下面是之后会用到的代码:
<div id="test">
Warning: This element contains <code>code</code> and <strong>strong language</strong>.
var x = document.getElementById('test');
innerHTML
赋值操作:先对值内容进行模式匹配,然后把处理后的值赋予给 innerHTML 属性。
模式匹配结果将导致 保留 和 将字符转换为 HTML 实体 两个操作。
以下情况将被保留:
HTML 实体(ASCII 实体、符号实体和字符实体)的实体名或实体编号;
符号实体和字符实体对应的字符;
没有 HTML 实体与之对应的字符;
HTML 标签。(如<img>
)
以下情况将会执行字符转换为 HTML 实体:
ASCII 实体对应的字符(<、>、&)。
也就是说除了单独的 <、>、&会被转换为实体名外,将原封不动地将值赋予给 innerHTML 属性。
取值操作:直接获取 innerHTML 属性值。
x.innerHTML
// => "
// => Warning: This element contains <code>code</code> and <strong>strong language</strong>.
// => "
innerText
赋值操作:先将 ASCII 实体对应的字符(<、>、&)转换为实体名,然后把处理后的值赋予给 innerHTML 属性。
取值操作:innerText 的取值实际上就是对 innerHTML 的属性值进行一系列处理,然后返回,具体步骤如下
对 HTML 标签进行解析;
对 CSS 样式进行带限制的解析和渲染;
将 ASCII 实体转换为对应的字符;
剔除格式信息(如\t、\r 和\n 等),将多个连续的空格合并为一个。
注意,innerText 返回浏览器渲染的内容,具体来说:
innerText 会忽略行内样式和脚本。
innerText 只返回可见的元素。
会触发 reflow。
x.innerText
// => "Warning: This element contains code and strong language."
textContent
赋值操作:先将 ASCII 实体对应的字符(<、>、&)转换为实体名,然后把处理后的值赋予给 innerHTML 属性。
取值操作:textContent 的取值实际上就是对 innerHTML 的属性值进行一系列处理,然后返回,具体步骤如下
对 HTML 标签进行剔除;
将 ASCII 实体转换为相应的字符。
对 HTML 标签是剔除不是解析,也不会出现 CSS 解析和渲染的处理,因此<br/>
等元素是不生效的。
不会剔除格式信息和合并连续的空格,因此\t、\r、\n 和连续的空格将生效。
textContent 会原样返回行内样式和脚本。
不会触发 reflow。
x.textContent
// => "
// => Warning: This element contains code and strong language.
// => "
了解了上面说的几个 api,我们就可以动手实现实体字符转义了:
//仅限于包含`&、<、>`的文本转换
function stringToEntity(str){
var div=document.createElement('div');
div.innerText=str;
var res=div.innerHTML;
return res;
其实除了 innerText,还可以通过创建文本节点的方式来完成转义,即使用 document.createTextNode(),完整版代码如下:
// 将HTML转义为实体
function escape(html){
var elem = document.createElement('div')
var txt = document.createTextNode(html)
elem.appendChild(txt)
return elem.innerHTML;
// 将实体转回为HTML
function unescape(str) {
var elem = document.createElement('div')
elem.innerHTML = str
return elem.innerText || elem.textContent
这种方法有个缺陷是只能转义“< > & ”,对于单引号,双引号都不转义。另外一些非 ASCII 也不能转义。利用浏览器内部 API 就行了转义和转回(主流浏览器都支持),不具完整性,很明显只能在浏览器环境中使用(比如不能在 Node.js 中跑)。
会自动对文本中存在的 HTML 语法字符(小于号、大于号、和号)进行编码的节点的 innerText 属性。其原理是设置 innerText 会生成当前节点的一个子文本节点(这里相当于调用了 createTextNode),而为了确保只生成一个子文本节点,就需要对文本进行 HTML 编码。innerHTML 虽然也可以做到,但它转变的只是标签中的文本。下面的例子展示了它们的不同。
var div=document.createElement('div');
div.innerText='<p>hello & world</p>';
div.innerText //<p>hello & world</p>"
div.innerHTML //"<p>hello & world</p>"
div.innerHTML='<p>hello & < world</p>'
div.innerHTML //"<p>hello & < world</p>"
div.innerText //"hello & < world"
从上面例子中可以看到二者的区别:innerText 会将所有的文本转义(当然也不是全部文本,比如空格就不会),innerHTML 则是对标签內的文本进行转义,标签如<p>
就不会转义,但孤立的小于大于号还是会进行转换的。
所谓 HTML 编码其实就是将字符转换为 HTML 实体,这是防止脚本注入的重要手段之一。
在实际开发过程中,我们会使用成熟的 xss 过滤库,而其本质离不开本文所说的方法,那么在阅读完本文后大家有没有对这块知识更加对深入理解一点呢。