在原生JavaScript中使用类似createElement的函数而非innerHTML/insertAdjacentHTML的原因
嘿,这个问题问得特别实在——我当初从React转写原生JS项目的时候,也纠结过好一阵子:明明用innerHTML加模板字符串写起来更短,为啥还要费劲写createElement那一套?后来在踩了不少坑之后,才明白这俩方案的适用场景差挺多的,下面就给你唠唠用createElement风格方案的核心优势:
1. 安全性:从根源避免XSS攻击
这绝对是最关键的一点!如果你的代码里要插入用户输入的内容(比如评论、用户名、搜索关键词),用innerHTML/insertAdjacentHTML简直是给自己埋雷。因为这两个方法会把传入的字符串直接当作HTML解析执行,如果用户输入了恶意代码(比如<img src=x onerror="stealCookies()">这种),浏览器会老老实实执行里面的脚本,分分钟遭遇XSS攻击。
举个直观的对比:
- 危险的innerHTML写法:
const userInput = '<img src=x onerror="alert(\'你的页面被攻陷了!\')">'; document.body.innerHTML = userInput; // 直接触发弹窗! - 安全的createElement写法:
用textContent(或者createTextNode)会自动转义所有HTML特殊字符,恶意代码只会变成普通文本,根本没机会作妖。const userInput = '<img src=x onerror="alert(\'你的页面被攻陷了!\')">'; const container = document.createElement('div'); container.textContent = userInput; // 只会把内容当作纯文本显示,不会执行任何代码 document.body.appendChild(container);
2. 更精准的DOM元素操控
用createElement创建元素时,你直接拿到的是真实的DOM节点引用,可以立刻给它绑定事件、修改属性、调整样式,后续要再操作这个元素也不用再去页面里查询。
比如:
const toggleBtn = document.createElement('button'); toggleBtn.textContent = '切换主题'; toggleBtn.classList.add('theme-btn'); // 直接绑定事件,不用后续查询 toggleBtn.addEventListener('click', () => { document.body.classList.toggle('dark-theme'); }); // 还能随时修改属性 toggleBtn.disabled = false; document.body.appendChild(toggleBtn);
要是用innerHTML的话,你得先把HTML插进去,再用querySelector去捞这个按钮——不仅多了一步,还容易因为页面结构变动捞错元素,或者压根捞不到,特别闹心。
3. 性能优化:减少不必要的重绘重排
当你需要批量创建元素时,createElement配合DocumentFragment可以把重绘重排的次数降到最低。比如要创建1000个列表项:
const fragment = document.createDocumentFragment(); for (let i = 0; i < 1000; i++) { const li = document.createElement('li'); li.textContent = `列表项 ${i + 1}`; fragment.appendChild(li); } // 只把fragment插入一次,触发一次重排 document.querySelector('.list-container').appendChild(fragment);
而用innerHTML的话,你要么循环拼接字符串(数据量大的时候字符串拼接本身就有性能开销),要么一次性插入,但innerHTML会先销毁目标节点下的所有子元素再重建,要是你只是追加元素而非替换,这就属于完全没必要的额外开销了。
4. 大型项目的可维护性更强
在规模大一点的项目里,用createElement(或者自己封装的DOM创建函数)能让代码更模块化、更易复用。比如你可以封装一个通用的按钮创建函数:
function createPrimaryButton(text, onClick) { const button = document.createElement('button'); button.textContent = text; button.classList.add('primary-btn', 'rounded'); button.addEventListener('click', onClick); return button; }
之后每次需要按钮的时候,直接调用createPrimaryButton('提交', handleSubmit)就行,不用重复写一堆HTML字符串,也不会因为字符串拼接时的引号不匹配、标签没闭合这种小错误浪费时间排查。
而用innerHTML的话,HTML字符串越长,越容易藏着这种低级错误,而且修改起来也麻烦——要改个样式,得在字符串里找对应的class;要加个事件,还得先插入再查元素,完全不如直接操作DOM节点顺手。
5. 避免意外的副作用
用innerHTML插入HTML的时候,浏览器会解析里面的所有内容,包括<style>、<script>甚至是自定义属性,很可能会意外影响页面已有的样式或行为。比如你插入的HTML里带了个<style>body { background: red !important; }</style>,整个页面的背景色就全变了,这种意外在大型项目里排查起来特别头疼。
而createElement创建的元素是“干净”的,你给它加什么属性、样式、事件,完全在你的控制范围内,不会出现这种莫名其妙的副作用。
备注:内容来源于stack exchange,提问作者mohammad ghohestani

