一、DOMTokenList 接口概述
DOMTokenList 接口表示一组空格分隔的标记,它在 Web 开发中扮演着重要角色。最常见的应用场景是通过 Element.classList 获取元素的类名列表,此外 HTMLLinkElement.relList、HTMLAnchorElement.relList 等属性也返回 DOMTokenList 对象。该接口与 JavaScript Array 对象类似,索引从 0 开始,并且始终区分大小写。
javascript
// 获取元素的 class 列表
const element = document.querySelector('div');
const classList = element.classList;
console.log(classList); // DOMTokenList ["class1", "class2", ...]
// 获取链接的 rel 列表
const link = document.querySelector('a');
const relList = link.relList;
console.log(relList); // DOMTokenList ["noopener", "noreferrer"]
// DOMTokenList 的特性:区分大小写
element.className = 'ACTIVE active Active';
const tokens = element.classList;
console.log(tokens); // ["ACTIVE", "active", "Active"] - 三个不同的值
// 检查长度
console.log(tokens.length); // 3
// 通过索引访问
console.log(tokens[0]); // "ACTIVE"
console.log(tokens[1]); // "active"
二、DOMTokenList 的属性
DOMTokenList 接口提供了两个非常有用的属性。length 属性是只读的,返回列表中值的个数,帮助开发者了解当前列表的大小。value 属性则返回以字符串形式表示的整个列表,各标记之间用空格分隔。
javascript
// length 属性示例
const div = document.createElement('div');
div.className = 'class1 class2 class3';
const classList = div.classList;
console.log(classList.length); // 3
// 添加新类名后长度变化
classList.add('class4');
console.log(classList.length); // 4
// 移除类名后长度变化
classList.remove('class2');
console.log(classList.length); // 3
// value 属性示例
console.log(classList.value); // "class1 class3 class4"
// 通过 value 属性直接设置整个列表
classList.value = 'new1 new2 new3';
console.log(classList);
// DOMTokenList(3) ["new1", "new2", "new3"]
// value 属性的实际应用:将类名列表作为字符串存储
const storedClasses = classList.value;
localStorage.setItem('elementClasses', storedClasses);
// 从存储中恢复
const restored = localStorage.getItem('elementClasses');
if (restored) {
const newDiv = document.createElement('div');
newDiv.className = restored;
console.log(newDiv.classList.value); // 恢复的类名列表
}
三、item 方法:按索引访问
item 方法允许开发者根据索引值获取列表中对应位置的标记,当索引超出范围时返回 null。这个方法与使用数组索引访问的主要区别在于,item 方法在所有浏览器中都有统一的行为,而直接使用方括号访问在某些边缘情况下可能表现不一致。
javascript
// item 方法的基本使用
const container = document.createElement('div');
container.className = 'header main content responsive';
const tokens = container.classList;
// 使用 item 方法获取特定索引的标记
console.log(tokens.item(0)); // "header"
console.log(tokens.item(1)); // "main"
console.log(tokens.item(2)); // "content"
console.log(tokens.item(3)); // "responsive"
// 超出范围返回 null
console.log(tokens.item(99)); // null
// 遍历所有标记的通用模式
for (let i = 0; i < tokens.length; i++) {
const token = tokens.item(i);
console.log(`索引 ${i}: ${token}`);
}
// 获取最后一个标记的实用函数
function getLastToken(tokenList) {
const lastIndex = tokenList.length - 1;
return lastIndex >= 0 ? tokenList.item(lastIndex) : null;
}
console.log(getLastToken(tokens)); // "responsive"
// 空列表的情况
const emptyDiv = document.createElement('div');
const emptyTokens = emptyDiv.classList;
console.log(emptyTokens.item(0)); // null
console.log(getLastToken(emptyTokens)); // null
四、contains 方法:检查标记是否存在
contains 方法用于判断 DOMTokenList 中是否包含指定的标记,返回布尔值。这个方法在处理条件样式、验证状态时非常有用,是开发中常用的方法之一。需要注意的是,由于大小写敏感,检查时必须使用与原始标记完全相同的大小写。
javascript
// contains 方法的基本使用
const card = document.createElement('div');
card.className = 'card primary large shadow';
// 检查是否包含特定类名
console.log(card.classList.contains('card')); // true
console.log(card.classList.contains('primary')); // true
console.log(card.classList.contains('small')); // false
// 大小写敏感的演示
card.classList.add('ACTIVE');
console.log(card.classList.contains('active')); // false
console.log(card.classList.contains('ACTIVE')); // true
// 实际应用场景:条件性样式切换
function toggleTheme(element, isDark) {
if (isDark) {
if (!element.classList.contains('dark-theme')) {
element.classList.remove('light-theme');
element.classList.add('dark-theme');
}
} else {
if (!element.classList.contains('light-theme')) {
element.classList.remove('dark-theme');
element.classList.add('light-theme');
}
}
}
// 验证元素是否具有特定样式类
function hasResponsiveStyles(element) {
return element.classList.contains('responsive') ||
element.classList.contains('mobile-friendly');
}
// 表单验证中的使用
const input = document.querySelector('input');
function validateInput() {
if (input.value.length < 3) {
if (!input.classList.contains('invalid')) {
input.classList.add('invalid');
}
return false;
} else {
if (input.classList.contains('invalid')) {
input.classList.remove('invalid');
}
return true;
}
}
五、add 方法:添加标记
add 方法用于向 DOMTokenList 中添加一个或多个标记。该方法会自动处理重复项,如果添加的标记已经存在,则会被自动忽略。同时,add 方法也会自动处理空格,确保列表始终保持正确的空格分隔格式。当传入空字符串或包含空白字符的标记时,会抛出相应的异常。
javascript
// add 方法的基本使用
const panel = document.createElement('div');
panel.className = 'panel';
// 添加单个标记
panel.classList.add('collapsed');
console.log(panel.classList.value); // "panel collapsed"
// 添加多个标记
panel.classList.add('primary', 'shadow', 'rounded');
console.log(panel.classList.value); // "panel collapsed primary shadow rounded"
// 重复添加会被自动忽略
panel.classList.add('primary');
console.log(panel.classList.value); // 仍然是 "panel collapsed primary shadow rounded"
// 实际应用:根据状态添加多个类名
function setElementState(element, state) {
// 先清除所有状态类
const stateClasses = ['loading', 'success', 'error', 'warning'];
stateClasses.forEach(cls => element.classList.remove(cls));
// 添加新状态类
if (state === 'loading') {
element.classList.add('loading', 'disabled');
} else if (state === 'success') {
element.classList.add('success', 'fade-in');
} else if (state === 'error') {
element.classList.add('error', 'shake');
}
}
// 批量添加的实用技巧
function addMultipleClasses(element, classNames) {
// classNames 可以是数组或空格分隔的字符串
if (Array.isArray(classNames)) {
element.classList.add(...classNames);
} else if (typeof classNames === 'string') {
const classes = classNames.split(' ').filter(c => c.trim());
element.classList.add(...classes);
}
}
const box = document.createElement('div');
addMultipleClasses(box, ['box', 'highlight', 'border']);
console.log(box.classList.value); // "box highlight border"
addMultipleClasses(box, 'extra large padding');
console.log(box.classList.value); // "box highlight border extra large padding"
六、remove 方法:移除标记
remove 方法用于从 DOMTokenList 中移除一个或多个指定的标记。如果列表中不存在要移除的标记,该方法不会报错,也不会有任何变化。remove 方法返回 undefined,因此不适合用于条件判断,应该使用 contains 方法来检查标记是否存在。
javascript
// remove 方法的基本使用
const modal = document.createElement('div');
modal.className = 'modal open visible animated dark';
const classList = modal.classList;
console.log(classList.value); // "modal open visible animated dark"
// 移除单个标记
classList.remove('dark');
console.log(classList.value); // "modal open visible animated"
// 移除多个标记
classList.remove('open', 'animated');
console.log(classList.value); // "modal visible"
// 移除不存在的标记 - 不会报错
classList.remove('non-existent');
console.log(classList.value); // "modal visible"
// 实际应用场景:清理临时类
function cleanupTemporaryClasses(element) {
const tempClasses = ['hover', 'focus', 'active', 'loading'];
tempClasses.forEach(cls => element.classList.remove(cls));
}
// 切换功能的实现
function toggleButtonState(button, isLoading) {
if (isLoading) {
button.classList.add('loading');
button.classList.remove('ready');
button.disabled = true;
} else {
button.classList.remove('loading');
button.classList.add('ready');
button.disabled = false;
}
}
// 批量移除的实用工具
function removeClassPatterns(element, patterns) {
const classList = Array.from(element.classList);
classList.forEach(className => {
patterns.forEach(pattern => {
if (className.includes(pattern)) {
element.classList.remove(className);
}
});
});
}
const demoElement = document.createElement('div');
demoElement.className = 'btn btn-primary btn-large btn-icon';
removeClassPatterns(demoElement, ['primary', 'icon']);
console.log(demoElement.classList.value); // "btn btn-large"
七、replace 方法:替换标记
replace 方法用于将列表中的一个已存在的标记替换为新的标记。如果第一个参数在列表中不存在,replace 方法会返回 false,并且不会将新的标记添加到列表中。如果替换成功,则返回 true。这个方法在处理类名迁移或样式重构时特别有用。
javascript
// replace 方法的基本使用
const alert = document.createElement('div');
alert.className = 'alert warning fade';
const classList = alert.classList;
// 替换存在的标记
const replaced = classList.replace('warning', 'error');
console.log(replaced); // true
console.log(classList.value); // "alert error fade"
// 替换不存在的标记 - 返回 false 且不添加新标记
const notReplaced = classList.replace('non-existent', 'new-class');
console.log(notReplaced); // false
console.log(classList.value); // 仍然是 "alert error fade"
// 实际应用场景:状态迁移
function migrateElementState(element, oldState, newState) {
const oldClass = `state-${oldState}`;
const newClass = `state-${newState}`;
if (element.classList.contains(oldClass)) {
element.classList.replace(oldClass, newClass);
return true;
}
return false;
}
// 批量查找并替换
function replaceMultipleClasses(element, replacements) {
const results = [];
for (const [oldToken, newToken] of Object.entries(replacements)) {
const success = element.classList.replace(oldToken, newToken);
results.push({ oldToken, newToken, success });
}
return results;
}
const container = document.createElement('div');
container.className = 'size-md color-dark theme-old';
const changes = replaceMultipleClasses(container, {
'size-md': 'size-lg',
'color-dark': 'color-light',
'theme-old': 'theme-new'
});
console.log(changes);
// [
// { oldToken: "size-md", newToken: "size-lg", success: true },
// { oldToken: "color-dark", newToken: "color-light", success: true },
// { oldToken: "theme-old", newToken: "theme-new", success: true }
// ]
console.log(container.classList.value); // "size-lg color-light theme-new"
八、toggle 方法:切换标记
toggle 方法是 DOMTokenList 中最灵活的方法之一,它可以根据标记是否存在来决定添加或移除。当只传入一个参数时,如果标记存在则移除并返回 false,如果不存在则添加并返回 true。通过传入第二个布尔值参数 force,可以强制指定添加或移除行为,这在处理复杂的状态切换时非常有用。
javascript
// toggle 方法的基本使用
const button = document.createElement('button');
button.className = 'btn';
// 切换标记 - 不存在则添加
console.log(button.classList.toggle('active')); // true
console.log(button.classList.value); // "btn active"
// 再次切换 - 存在则移除
console.log(button.classList.toggle('active')); // false
console.log(button.classList.value); // "btn"
// 使用 force 参数强制添加
console.log(button.classList.toggle('hidden', true)); // true
console.log(button.classList.value); // "btn hidden"
// 使用 force 参数强制移除
console.log(button.classList.toggle('hidden', false)); // false
console.log(button.classList.value); // "btn"
// 实际应用场景:手风琴组件
class Accordion {
constructor(element) {
this.element = element;
this.content = element.querySelector('.accordion-content');
this.header = element.querySelector('.accordion-header');
this.init();
}
init() {
this.header.addEventListener('click', () => this.toggle());
}
toggle() {
const isExpanded = this.element.classList.toggle('expanded');
if (this.content) {
this.content.style.maxHeight = isExpanded ? this.content.scrollHeight + 'px' : '0';
}
return isExpanded;
}
open() {
if (!this.element.classList.contains('expanded')) {
this.element.classList.toggle('expanded', true);
this.content.style.maxHeight = this.content.scrollHeight + 'px';
}
}
close() {
this.element.classList.toggle('expanded', false);
this.content.style.maxHeight = '0';
}
}
// 主题切换示例
function setupThemeSwitch(toggleButton) {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
function updateTheme(useDark) {
document.documentElement.classList.toggle('dark-theme', useDark);
document.documentElement.classList.toggle('light-theme', !useDark);
}
// 初始化主题
updateTheme(prefersDark);
toggleButton.addEventListener('click', () => {
const isDark = document.documentElement.classList.contains('dark-theme');
updateTheme(!isDark);
});
}
九、遍历方法的使用
DOMTokenList 提供了多个遍历方法,包括 forEach、entries、keys 和 values,这些方法使得开发者可以方便地迭代处理列表中的所有标记。forEach 是最常用的遍历方法,而 entries、keys 和 values 返回迭代器对象,适用于需要使用 for...of 循环的场景。
javascript
// forEach 方法的使用
const navigation = document.createElement('nav');
navigation.className = 'nav main-nav sticky responsive';
const navClasses = navigation.classList;
// 使用 forEach 遍历所有类名
navClasses.forEach((className, index, list) => {
console.log(`索引 ${index}: ${className}`);
console.log(`列表长度: ${list.length}`);
});
// 使用 forEach 进行条件处理
navClasses.forEach(className => {
if (className.startsWith('nav')) {
console.log(`导航相关类: ${className}`);
}
});
// entries 方法的使用
const entriesIterator = navClasses.entries();
for (const [index, value] of entriesIterator) {
console.log(`entry[${index}] = ${value}`);
}
// keys 方法的使用
const keysIterator = navClasses.keys();
for (const index of keysIterator) {
console.log(`索引: ${index}`);
console.log(`对应的值: ${navClasses[index]}`);
}
// values 方法的使用
const valuesIterator = navClasses.values();
for (const value of valuesIterator) {
console.log(`类名: ${value}`);
}
// 实际应用:将所有类名转换为数组
function tokenListToArray(tokenList) {
const result = [];
tokenList.forEach(token => result.push(token));
return result;
}
// 或者使用扩展运算符
function tokenListToArrayModern(tokenList) {
return [...tokenList];
}
const classArray = tokenListToArray(navigation.classList);
console.log(classArray); // ["nav", "main-nav", "sticky", "responsive"]
// 过滤类名的实用函数
function filterClasses(element, predicate) {
const classList = element.classList;
const classesToRemove = [];
classList.forEach(className => {
if (!predicate(className)) {
classesToRemove.push(className);
}
});
classesToRemove.forEach(className => classList.remove(className));
}
// 只保留以特定前缀开头的类
const containerDiv = document.createElement('div');
containerDiv.className = 'btn btn-primary custom-large custom-shadow';
filterClasses(containerDiv, className => className.startsWith('custom'));
console.log(containerDiv.classList.value); // "custom-large custom-shadow"
通过以上内容的学习,我们全面掌握了 DOMTokenList 接口的各个方面,包括其属性、各种操作方法以及遍历方法。DOMTokenList 为操作元素的类名和其他标记集合提供了强大而便利的接口,是现代 Web 开发中不可或缺的工具。
想要解锁更多HTML 核心标签实战、前端零基础入门干货、开发避坑全指南吗?
持续关注,后续将更新CSS 布局实战、JavaScript 交互基础、全站导航开发等硬核内容,带你从新手快速进阶,轻松搞定前端开发!