从结构体或 html 字符串生成对应的元素对象
从对象生成
首先是定义一个简单的结构体对象 ElObject
,其中要求格式与我们需要的一致:
tag
为一个有效的标签名 (tagName
)children
的类型为Array<ElObject|string>
attrs
的类型为null|Record<string, any>
一个简单的实现如下:
function createElement(obj) {
const root = document.createElement(obj.tag);
if (obj.attrs) {
Object.assign(root, obj.attrs);
}
if (obj.children) {
for (const child of obj.children) {
if (typeof child === 'string') {
root.appendChild(document.createTextNode(child));
} else {
root.appendChild(createElement(child));
}
}
}
return root;
}
// 调用
document.body.appendChild(createElement({
tag: 'p',
attrs: null,
children: ["快来访问", {
tag: 'a',
attrs: {
href: "https://jixun.moe/",
},
children: ["我的网站"],
}, "吧!"],
}));
从字符串生成
可以利用新的 ES 特性 - Tagged templates,来防止一些简单的 XSS 注入:
// 解析 html 字符串到对象
function parseHTML(html) {
const root = document.createElement('div');
root.innerHTML = html;
return root.childNodes[0];
}
// 使用 Tagged templates
function html(strings, ...params) {
// 建立一个临时的用于 HTML 编码的函数和元素
const escapeEl = document.createElement('div');
const escapeHTML = (text) => {
escapeEl.textContent = text;
return escapeEl.innerHTML;
};
// 拼接内容
const parts = [];
for(const [i, htmlPart] of strings.entries()) {
parts.push(htmlPart);
if (params[i]) {
parts.push(escapeHTML(params[i]));
}
}
// 拼接各部分然后解析 HTML 元素
return parseHTML(parts.join(''));
}
// 用户名
const name = "I love xss <img src='' onerror='alert(1)' />";
// 使用 html 函数来防止简单的 XSS 注入:
document.body.appendChild(html`<p>Hello! ${name}</p>`);
// 直接解析并插入,可以看到弹窗信息:
document.body.appendChild(parseHTML(`<p>Hello! ${name}</p>`));