如果说 DOM 节点是页面的骨架,那属性与样式就是赋予它们外貌与行为的血肉。无论是修改按钮的禁用状态、读取输入框的当前值,还是动态改变一个元素的颜色、大小、显示隐藏,都离不开这一节的内容。
13.3.1 原生属性(标准属性)
每个 DOM 元素都对应一个特定的 HTML 标签,而标签上定义的属性(如 id、src、href、type、value)会在 DOM 对象上以属性(property)的形式存在。JavaScript 提供了多种方式操作它们,最常用也最直接的就是“点操作”和 setAttribute/getAttribute 方法。
直接属性访问(推荐)
对于标准属性,直接通过 . 操作符读写是最简单、性能也最好的方式:
const link = document.querySelector('a');
console.log(link.href); // 获取完整解析后的 URL
link.href = '/new-page'; // 设置新地址
const input = document.querySelector('input');
input.value = '新内容'; // 修改输入框内容
input.disabled = true; // 禁用输入框
注意事项:
- 标准属性在 DOM 对象上都有对应的 property,名称大多与 HTML 属性相同,但有几个特殊情况,例如
class在 JavaScript 中对应className(因为class是保留字),for对应htmlFor(label 的 for 属性)。 - 对于布尔类型的属性(
disabled、checked、selected),直接赋true或false即可,无需移除整个属性。 - 属性值类型不一定是字符串:
style返回一个 CSSStyleDeclaration 对象,onclick返回一个函数或 null。
getAttribute / setAttribute / removeAttribute
这套方法直接操作的是 HTML 标签上的特性(attribute),得到的始终是字符串(或 null)。它与 DOM property 在大部分情况下同步,但并非完全等价。
const el = document.querySelector('.box');
el.setAttribute('data-id', '123');
console.log(el.getAttribute('data-id')); // '123'
el.removeAttribute('data-id');
property 与 attribute 的关键差异:
- 同步性:对于标准的、非布尔属性,二者通常保持双向同步。比如你改了
input.value的 property,getAttribute('value')返回的仍然是标签上的初始值,而不是当前值。valueproperty 代表实时值,而 attribute 保持默认值。 - 类型不同:property 可以是任意类型,attribute 始终是字符串。
- 大小写:HTML 属性不区分大小写,在 JavaScript 中通常用小写,自定义属性建议全小写并用连字符分隔(data-xxx)。
何时用 attribute 方法?
- 需要获取或设置自定义
data-*属性时(虽然也可以用dataset)。 - 需要移除某个属性,而不只是将其值置空或置为 false 时。
- 操作一些不常用、没有直接映射 property 的属性时(如
aria-*、colspan)。
13.3.2 自定义属性(data-*)
HTML5 引入了 data-* 属性,允许开发者在标签上存储一些不用于视觉呈现的、与元素相关的自定义数据。比如商品 ID、用户编号、配置项等。
<div id="product" data-product-id="1024" data-price="99.9" data-in-stock="true">
商品名称
</div>
通过 dataset 进行操作(推荐):
const product = document.getElementById('product');
// 读取
console.log(product.dataset.productId); // "1024"
console.log(product.dataset.price); // "99.9"
console.log(product.dataset.inStock); // "true"
// 设置
product.dataset.discount = '0.8';
// 对应生成 data-discount="0.8" 属性
// 删除
delete product.dataset.inStock;
dataset 的优势:
- 自动进行驼峰转换:
data-product-id变成dataset.productId。 - 直接返回属性值,无需手动调用
getAttribute。 - 设置后立即可在 DevTools 中看到 HTML 标签上的变化。
注意事项:
dataset的值永远是字符串(或undefined),即使你存的是true,读回来也是"true",需要自行转换类型。- 如果属性名中包含多个连字符,如
data-order-status-code,会转换为驼峰:dataset.orderStatusCode。 - 不建议在
data-中存储大量数据或敏感信息,因为它在 HTML 中是完全公开可见的。大规模数据应使用 JavaScript 对象管理,或借助data-存放标识符再映射。
13.3.3 行内样式(Inline Styles)
每个 DOM 元素都有一个 style 属性,它是一个 CSSStyleDeclaration 对象,用于读写元素的行内样式(即写在标签 style 属性中的 CSS)。
const box = document.querySelector('.box');
box.style.backgroundColor = 'red';
box.style.fontSize = '20px';
box.style.display = 'none';
命名规则:
- CSS 属性名中带连字符的,转换为驼峰写法:
background-color→backgroundColor,margin-top→marginTop。 - 可以使用
cssText一次性设置多个样式:
box.style.cssText = 'color: white; background: black; padding: 10px;';
但 cssText 会完全覆盖元素已有的行内样式,合并时需谨慎,可以在末尾添加 ; 后累加,或使用工具函数合并。
获取计算后的样式:
行内样式只反映直接写在 style 属性上的值,无法获得从 CSS 规则层叠而来的最终样式。要获取元素当前实际应用的样式(包括继承自样式表的),使用 getComputedStyle():
const computed = getComputedStyle(box);
console.log(computed.backgroundColor); // 返回最终的 rgb/rgba 字符串
这个方法返回的对象是只读的。
性能提示:
- 频繁的逐个修改
style.xxx会导致浏览器多次重排/重绘。如果需要批量修改样式,建议使用cssText或切换类名来减少重排次数。 - 避免在循环中读写样式属性而引起“强制同步布局”(例如先读取
offsetWidth,再修改样式),这会在第 18 章渲染原理中详细解释。
13.3.4 类名操作(classList)
样式管理最推荐的方式不是直接操作行内样式,而是通过 类名 来控制。JavaScript 提供了 classList 对象,它提供了一套优雅的 API 来增删改查元素的类名,无需手动拼接字符串。
const el = document.querySelector('.item');
el.classList.add('active', 'highlight'); // 添加一个或多个类
el.classList.remove('hidden'); // 移除类
el.classList.toggle('dark-mode'); // 切换类(有则移除,无则添加)
el.classList.toggle('dark-mode', isDark); // 第二个参数控制强制添加/移除
el.classList.contains('active'); // 检查是否包含某类,返回布尔值
el.classList.replace('old', 'new'); // 替换类名
与传统 className 的对比:
className是一个字符串,操作类时需手动拆分、添加、去重、拼接,容易出错、冗余。classList自动处理空格分隔、重复检查等问题,代码更健壮,可读性更高。
应用场景示例:
// 点击切换菜单的展开/收起
button.addEventListener('click', () => {
menu.classList.toggle('open');
});
// 根据数据动态设置状态样式
if (item.isActive) {
itemEl.classList.add('active');
} else {
itemEl.classList.remove('active');
}
性能与最佳实践:
- 尽量在 CSS 中定义好不同状态的样式,然后用 JavaScript 切换类名来改变外观,而不是直接写行内样式。这样样式与逻辑解耦,更易维护,也更利于浏览器优化渲染(类名变更通常只引起重绘或最小幅度重排)。
- 若需要在多个元素间切换同一个类名,可以使用循环或结合事件委托。
13.3.5 综合运用与常见陷阱
结合前面几种操作,我们来做一个小结示例,展示如何优雅地、同时使用属性和样式来构建一个常见的交互效果——表单实时校验。
<input type="text" id="username" data-min-length="3" placeholder="请输入用户名">
<span id="error" class="error-msg hidden">长度至少3个字符</span>
<script>
const input = document.getElementById('username');
const error = document.getElementById('error');
input.addEventListener('input', function() {
const minLength = parseInt(this.dataset.minLength, 10);
const isValid = this.value.length >= minLength;
// 操作类名切换错误提示的显示
error.classList.toggle('hidden', isValid);
// 操作原生属性修改输入框状态
input.style.borderColor = isValid ? '#ccc' : 'red';
// 甚至可以操作自定义属性来记录校验结果
input.dataset.valid = isValid;
});
</script>
常见易错点:
- 混淆 property 和 attribute:例如,想获取复选框是否选中,应该用
checkbox.checked而不是getAttribute('checked'),因为后者返回的是 HTML 初始状态字符串("checked" 或 null),不会随着用户操作改变。 classList.toggle在老旧浏览器中不支持第二个参数,在需要强制指定时可通过add/remove替代。style.float是保留字冲突,应使用cssFloat。- 忘记数字加单位:设置
style.width时,不能只给数字,必须带上'px'等单位,除非属性本身是无单位的(如opacity、zIndex)。 - 读取计算样式后的字符串解析:
getComputedStyle返回的颜色通常是rgb()或rgba(),尺寸是'100px',需要解析数字时使用parseInt或parseFloat并注意单位。
掌握属性与样式的操作是成为高效前端开发者的基本功。良好的习惯是:用 dataset 存数据,用 classList 管外观,用 property 改状态。这不仅让代码清晰易读,也能帮你在日后的性能优化和状态管理中避开许多坑。