从结构体或 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: ["我的网站"],
  }, "吧!"],
}));

HTML 代码演示

从字符串生成

可以利用新的 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>`));

HTML 代码演示 XSS

Jixun

Jixun

学习与游戏的旅途,各类杂谈。