先记住这个答案
innerHTML 和 outerHTML 都是 Element 属性,用于读取或设置 HTML。读取 innerHTML 返回元素内部所有子节点的序列化;读取 outerHTML 则返回包含自身标签的序列化。赋值时,innerHTML 将新 HTML 解析为节点并替换全部子节点,不影响元素本身;outerHTML 会将整个元素替换为解析后的新节点,但原变量仍引用旧元素。若无父节点,设置 outerHTML 不生效。
- innerHTML 替换元素内部子节点。
- outerHTML 替换整个元素自身。
- 赋 outerHTML 后原引用仍指向旧节点。
读取与写入的序列化边界
读取时,innerHTML 对元素内部的子节点进行 HTML 序列化,不包括自身的开始和结束标签;outerHTML 则序列化整个元素,包括自身标签和属性。例如一个 <div id="d"><p>文本</p></div>,innerHTML 返回 <p>文本</p>,outerHTML 返回 <div id="d"><p>文本</p></div>。另外,属性值中的 & 等字符可能被转义,如 &。
写入时,innerHTML 将字符串解析为节点树并替换掉原有所有子节点,元素本身不变;outerHTML 则将整个元素替换为解析后的节点,相当于先移除原元素再插入新节点。但注意,变量仍持有旧元素引用,如果旧元素被替换,它不再位于文档流中,后续操作它不会影响页面。
场景:将 `<div>` 包裹的目标改为 `<section>`
前端迭代中,需要把某个 <div id="box"> 改成语义化标签 <section>,同时保留内部结构和事件委托。若只改内部或直接改 HTML 字符串,innerHTML 无法改变标签名。此时必须用 outerHTML,但赋值后原 box 引用脱离文档,需重新通过选择器获取新节点。实际操作:box.outerHTML = '<section id="box">' + box.innerHTML + '</section>',然后 box = document.querySelector('#box') 更新引用。
该方式虽能达到目的,但注意如果 box 内部存在绑定在自身的事件监听器,元素替换后监听器会丢失,因为监听器是针对旧节点的。若事件通过事件委托挂载在父级,则不受影响。因此决策时要评估事件体系,避免功能中断。
失效条件与安全限制
outerHTML 的赋值不总是成功:如果元素没有父节点,解析后的节点无处安放,赋值会静默失败,元素及其子节点绝不改变。类似地,对 document.documentElement 操作会抛出 NoModificationAllowedError,因为根元素不能直接被替换。文档片段中的孤立元素也是同样。
另一个边界是安全风险。两者都会将字符串解析为 HTML,因此可能成为 XSS 注入点。例如 <img src=x onerror=alert(1)> 会执行事件处理器。此外,设置 outerHTML 时输入必须是合法 HTML,否则可能被解析为意外结构。建议对不可信输入先做消毒,或使用 TrustedHTML 策略。
容易答错的地方
- 误以为外层替换后变量指向新元素
- 许多人认为
el.outerHTML = '<p>新</p>'后,el会变成新元素,但事实并非如此。规范明确说变量仍持有对原始元素的引用,原元素被 detach 后仍然存在。需要重新查询才能拿到新节点。 - 用 innerHTML 尝试修改元素标签名
- 有人试图通过
el.innerHTML = '<section>...'来改<div>为<section>,但这只会替换内部内容,外层标签依然是<div>。要改变标签本身只能借助 outerHTML 或手动创建新节点再替换。
面试官还会怎么问?
对一个没有父节点的元素设置 outerHTML 会怎样?
赋值无效,不会改变该元素及其后代。因为解析后的新节点没有插入点,浏览器直接忽略。变量依然指向原元素,内容和文档均无变化。
outerHTML 替换旧元素时,旧元素上的事件监听器会怎样?
监听器绑定在旧节点上,旧节点被移除文档后自然不在 DOM 中,事件不会再触发。除非监听器挂载在父级并通过事件委托,否则会丢失。因此替换前需解绑或调整事件处理策略。
innerHTML 和 outerHTML 在 XSS 防护上有何共同弱点?
两者都接受字符串并解析为 HTML,若字符串含 <img onerror> 之类可执行 javascript 的属性,就会触发 XSS。浏览器不允许直接执行 <script>,但事件属性可绕过。必须对输入消毒或可靠转义。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。