03元素修改
上一课我们学会了「找到元素」——querySelector 把元素抓回手里。
接下来第二步就是「改它」:改文字 textContent、
改 HTML innerHTML、改类名 classList、改样式 style、
改任意属性 attributes。这一课一次收齐,从此告别「只会改文本」的弱鸡玩法。
一、内容三件套:textContent / innerText / innerHTML
想改元素里面的「东西」,有三个长得像的 API——它们名字像,
脾气却完全不一样。textContent 是老实人,
innerHTML 是魔法师,innerText 是受样式影响的「近视眼」。
纯文本模式。原样输出,不会解析 HTML,永远安全。
HTML 模式。字符串里的标签会被解析渲染,强大也危险。
受 CSS 影响。display:none 的元素读不到,性能也最差。
| API | 解析 HTML? | 受 CSS 影响? | 安全性 | 性能 | 推荐度 |
|---|---|---|---|---|---|
| textContent | ✗ 当作纯文本 | ✗ 不受影响 | 安全,自动转义 | ✓ 最快 | 默认首选 |
| innerHTML | ✓ 解析为 DOM | ✗ 不受影响 | ✗ 用户输入会 XSS | 中等 | 场景合适再用 |
| innerText | ✗ 纯文本 | ✓ 受 display:none 影响 |
安全 | ✗ 最慢(要重排) | 几乎不用 |
<script> 标签,赋值时立刻执行。
凡是来自用户输入、URL 参数、接口数据的内容——
永远先用 textContent,或者用 textContent + DOM API 拼接。二、textContent 安全改造
日常 80% 的场景用 textContent 就够了。
它干三件事:设文本 / 清空内容 / 读取文本。
读取时返回元素「所有后代」的文本拼接,<style> 和
<script> 里的内容也算。
"写点啥都行 <b>前端</b>" 改成你自己的内容el.textContent = ''(最快)、el.innerHTML = ''(最常见)、el.replaceChildren()(ES2022,语义最清)。
性能上 textContent 略胜一筹。三、innerHTML 强大但要小心
innerHTML 是把字符串当 HTML 解析再插入,
适合「结构复杂的整块 HTML 字符串」。它的优势是一次写入、结构自由;
代价是不安全——用户输入直接插等于 XSS。
| 场景 | 推荐 | 理由 |
|---|---|---|
| 改个标题、改段文字 | textContent | 快、安全、不想要标签 |
| 用户输入、表单回显 | textContent | 绝对安全第一 |
| 服务端返回的可信 HTML 片段 | innerHTML | 结构复杂,字符串拼接方便 |
| 清空元素 | 两者皆可 | 前者更快,后者更常见 |
| 大块内容一次性写入 | innerHTML | 实测 innerHTML 大块时反而更快 |
<script> 直接执行、
<img onerror> 触发、<a href="javascript:..."> 跳转。
用户输入永远走 textContent 或 DOMPurify.sanitize(),
别心存侥幸。四、classList:类名管理神器
改元素的 class 不要再写 el.className += ' active' 了——
classList 是 DOM 给你的类名管理神器,
五个方法干所有事情。注意 classList 是
DOMTokenList,不是数组(没有 map、filter)。
| 方法 | 作用 | 示例 |
|---|---|---|
| add(cls) | 添加类(已存在则跳过) | el.classList.add('active') |
| remove(cls) | 移除类(不存在也不报错) | el.classList.remove('hidden') |
| toggle(cls, ?force) | 有则删、无则加;force=true 强制加 | el.classList.toggle('on', true) |
| contains(cls) | 是否存在某类(返回布尔) | el.classList.contains('active') |
| replace(old, new) | 替换类(不存在则返回 false) | el.classList.replace('a', 'b') |
Array.from(classList) ——把 box 改成 warning 看效果变化toggle 的第二参数非常实用:
el.classList.toggle('on', isMobile)——根据条件强制加/减。
想切换主题?写 document.body.classList.toggle('dark') 一行搞定。五、style 行内样式
想让元素动态变色、变大、移动?用 el.style.xxx。它直接改元素的
行内 style 属性,等同于 HTML 里写
style="color: red"。驼峰命名是唯一规则。
| 写法 | 对应 CSS | 示例 |
|---|---|---|
| 单属性 | color / font-size | el.style.color = 'red' |
| 复合属性(驼峰) | background-color / font-weight | el.style.backgroundColor = '#fff' |
| transform / 动画 | transform / transition | el.style.transform = 'scale(1.1)' |
| CSS 变量 | --main-color | el.style.setProperty('--x', '5px') |
| 批量赋值 | — | el.style.cssText = 'color:red; font-size:18px;' |
--accent);③ 改 transform 复合属性——注意 CSS 变量必须用 setPropertygetComputedStyle(el).color。
改 transform 时整段赋值:
el.style.transform = 'translateX(10px) scale(1.1)',不要单写一项。六、attributes 任意属性
除了 class 和 style,元素还有一堆任意 HTML 属性:
id / href / src / disabled / data-*…
四个方法搞定一切:getAttribute / setAttribute
/ removeAttribute / hasAttribute。
| API | 作用 | 示例 |
|---|---|---|
| getAttribute(name) | 取值(不存在返回 null) | el.getAttribute('href') |
| setAttribute(name, val) | 赋值 | el.setAttribute('id', 'main') |
| removeAttribute(name) | 移除 | el.removeAttribute('disabled') |
| hasAttribute(name) | 是否存在(布尔) | el.hasAttribute('hidden') |
| dataset.foo | 读 data-foo(驼峰) | el.dataset.userId ↔ data-user-id |
| 布尔属性 | hidden / disabled / checked | el.hidden = true(直接属性更顺) |
data-user-id;③ hasAttribute 检测;④ hidden 布尔属性切换——把 data-user-id 改成 data-product-id 同步改 dataset 字段dataset 是 DOM 给 data-* 属性开的语法糖:
data-product-id ↔ el.dataset.productId(连字符变小驼峰)。
自定义状态、配置项、Vue 的 data-v-xxx 都靠它传递。七、innerHTML vs DocumentFragment:批量插入的性能差距
想往页面塞 1000 个 <li>?两种写法天差地别:
逐个 appendChild 触发 1000 次重排,浏览器累死;
DocumentFragment 是「内存中的临时容器」,
攒完一波只触发 1 次重排。差距肉眼可见。
| 方式 | DOM 写入次数 | 重排次数 | 1000 项参考耗时 |
|---|---|---|---|
| 逐个 appendChild | 1000 次 | 可能 1000 次 | 慢(10ms+) |
| innerHTML 一次写 | 1 次 | 1 次 | 最快 |
| DocumentFragment 批量 | 1 次 | 1 次 | 接近 innerHTML,可控性更强 |
appendChild。
如果一定要动态创建,先丢进 DocumentFragment,最后一次挂载。
性能差距在 1000 项以上肉眼可见,1 万项可能差10 倍。八、本章小结
- 改文本默认用
textContent:安全、快、不解析 HTML。 innerHTML强大但有 XSS 风险:用户输入绝不走 innerHTML,走 textContent 或 DOMPurify。classList的五个方法(add/remove/toggle/contains/replace)替代老旧的className拼接。el.style.xxx改行内样式,复合属性驼峰命名;CSS 变量走setProperty。- 任意属性:
getAttribute/setAttribute/removeAttribute/hasAttribute四件套;data-*用dataset取。 - 大量 DOM 插入用 DocumentFragment 批量构建,最后一次挂载,避免多次重排。
- innerHTML 大块写入反而比逐个 appendChild 快;但可控性差。
document.body.classList.toggle('dark'),
配合 CSS 让 .dark 覆盖背景与文字色;
② 用 dataset 存数据——给 5 张卡片各自加 data-id data-name,
点击后用 JSON.stringify(card.dataset) 打印出来;
③ 用 DocumentFragment 批量插入——写一个循环生成 200 条留言(每条含头像、昵称、内容三段),
先丢进 Fragment 再一次性挂到列表,体会「不抖」的渲染;
④ XSS 实验:故意把 el.textContent 换成 el.innerHTML,
输入 <img src=x onerror=alert(1)> 看浏览器弹窗——记住这个教训。