问题
后端对提交的数据进行了一些转义操作,比如 <p><b>123&456</b></p>
这样的,会被转义成 <p><b>123&456</b></p>
然后前端请求数据时,这些数据需要反转义一下
解决
这是我目前用到的,不必担心性能问题,见后面的研究部分。
// 反转义 bug@& 这样的字符
export function HTMLDecode(text) {
var temp = document.createElement('div');
temp.innerHTML = text;
var output = temp.innerText || temp.textContent;
temp = null;
return output;
}
同事提供的方法,正则与扫描
// 抽离成可配置的匹配列表
const matchList = {
'<': '<',
'>': '>',
'&': '&',
'"': '"',
'"': '"',
''': "'",
};
// 字符过滤器
const HtmlFilter = (text) => {
let regStr = '(' + Object.keys(matchList).toString() + ')';
// ↑ ------------【*提取匹配列表 key 值*】.【组数转字符串】
regStr = regStr.replace(/,/g, ')|(');
// ↑ 通过匹配将其更新为正则的字符串类型
const regExp = new RegExp(regStr, 'g');
// ↑ ------- 字符串 转 正则 方法
return text.replace(regExp, (match) => matchList[match]);
// ↑ ------ 替换方法 (正则,当前 key => 返回当前被匹配的 key 值)
};
export default HtmlFilter;
网上的正则方法
//HTML 反转义
function HTMLDecode(text) {
var temp = document.createElement('div');
temp.innerHTML = text;
var output = temp.innerText || temp.textContent;
temp = null;
return output;
}
var tagText = '<p><b>123&456</b></p>';
var encodeText = HTMLEncode(tagText);
console.log(encodeText); //<p><b>123&456</b></p>
console.log(HTMLDecode(encodeText)); //<p><b>123&456</b></p>
研究与思考
对于第一个方法同事感觉会有性能问题(频繁的创建 dom 于销毁 dom)。但是出乎我意料的是,基准测试中,两者差距不大。
因为 var temp = document.createElement('div');
这段代码创建了一个 element
类型的变量,并没有挂载到页面上进行渲染,然后直接由浏览器进行了 AST 解析,所以不会有太大的性能问题。当然,正则的话,应该会更快。
我先贴一下基准测试代码,用例是 testObj
,需要检测一下有没有 Html
的富文本。
浏览器提取 innerText
测试用例
let test =
'反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符反转义 bug@& 这样的字符';
let testObj = {
test,
a: 1,
b: 'this is a <b>stringbug@&</b>',
c: {
test,
a: 1,
b: 'this is a <b>stringbug@&</b>',
},
};
// 反转义 bug@& 这样的字符
function HTMLDecode(text) {
var temp = document.createElement('div');
temp.innerHTML = text;
var output = temp.innerText || temp.textContent;
temp = null;
return output;
}
// 判断字符串是不是 html 标签
function isHTML(str) {
var a = document.createElement('div');
a.innerHTML = str;
for (var c = a.childNodes, i = c.length; i--; ) {
if (c[i].nodeType === 1) return true;
}
return false;
}
// 深度遍历
function deepHTMLDecode(data) {
return _.cloneDeepWith(data, (value) => {
if (_.isString(value)) {
if (!isHTML(value)) {
return HTMLDecode(value);
}
}
});
}
// 深度遍历 - 正则
function deepHTMLDecodeHtmlFilter(data) {
return _.cloneDeepWith(data, (value) => {
if (_.isString(value)) {
if (!isHTML(value)) {
return HtmlFilter(value);
}
}
});
}
正则方法,没有去递归对象
// 抽离成可配置的匹配列表
const matchList = {
'<': '<',
'>': '>',
'&': '&',
'"': '"',
'"': '"',
''': "'",
};
// 字符过滤器
const HtmlFilter = (text) => {
let regStr = '(' + Object.keys(matchList).toString() + ')';
// ↑ ------------【*提取匹配列表 key 值*】.【组数转字符串】
regStr = regStr.replace(/,/g, ')|(');
// ↑ 通过匹配将其更新为正则的字符串类型
const regExp = new RegExp(regStr, 'g');
// ↑ ------- 字符串 转 正则 方法
return text.replace(regExp, (match) => matchList[match]);
// ↑ ------ 替换方法 (正则,当前 key => 返回当前被匹配的 key 值)
};
// 判断字符串是不是 html 标签
function isHTML(str) {
var a = document.createElement('div');
a.innerHTML = str;
for (var c = a.childNodes, i = c.length; i--; ) {
if (c[i].nodeType === 1) return true;
}
return false;
}
// 深度遍历 - 正则
function deepHTMLDecodeHtmlFilter(data) {
return _.cloneDeepWith(data, (value) => {
if (_.isString(value)) {
if (!isHTML(value)) {
return HtmlFilter(value);
}
}
});
}
输出结果
Div 标签创建与递归 x 52,463 ops/sec ±1.75% (64 runs sampled)
fanzhuanyi.js:91 正则方法 x 63,506 ops/sec ±0.81% (66 runs sampled)
fanzhuanyi.js:94 Fastest is 正则方法
很明显,正则方法比较快。
其他代码
转义
//HTML 转义
function HTMLEncode(html) {
var temp = document.createElement('div');
temp.textContent != null
? (temp.textContent = html)
: (temp.innerText = html);
var output = temp.innerHTML;
temp = null;
return output;
}
var tagText = '<p><b>123&456</b></p>';
console.log(HTMLEncode(tagText)); //<p><b>123&456</b></p>
function html_encode(str) {
var s = '';
if (str.length == 0) return '';
s = str.replace(/&/g, '&');
s = s.replace(/</g, '<');
s = s.replace(/>/g, '>');
s = s.replace(/ /g, ' ');
s = s.replace(/\'/g, ''');
s = s.replace(/\"/g, '"');
s = s.replace(/\n/g, '<br/>');
return s;
}
function html_decode(str) {
var s = '';
if (str.length == 0) return '';
s = str.replace(/&/g, '&');
s = s.replace(/</g, '<');
s = s.replace(/>/g, '>');
s = s.replace(/ /g, ' ');
s = s.replace(/'/g, "'");
s = s.replace(/"/g, '"');
s = s.replace(/<br\/>/g, '\n');
return s;
}
console.log(html_decode('<div>123</div>'));
console.log(html_encode(html_decode('<div>123</div>')));
Vue 里面还可以用自定义指令或者 Filters 转义
自定义指令
Vue.directive('HTMLDecode', {
bind(el, binding, vnode) {
// 获取按钮权限
const text = el.innerText || el.textContent;
el.innerHTML = text;
},
});
Filters
Vue.filter('HTMLDecode', function (value) {
if (!value) return '';
return HTMLDecode(value);
});