跟着 MDN 学 HTML day_42:(DOMTokenList 接口详解)

一、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 交互基础、全站导航开发等硬核内容,带你从新手快速进阶,轻松搞定前端开发!

相关推荐
前端 贾公子1 小时前
响应式系统基础:基于依赖追踪的响应式系统的本质(下)
前端·javascript·vue.js
幽络源小助理1 小时前
团队个人科技主页HTML源码 黑客帝国风格个人简历网页模板
前端·科技·html
杨超越luckly1 小时前
Python应用指南:百度热搜数据
python·百度·html·数据可视化
打工人小夏1 小时前
使用finalshell在新服务器上部署前端页面
linux·服务器·前端·vue.js
恋猫de小郭1 小时前
2026 Android I/O ,全新 AI 手机、 Android PC 和车载驾驶
android·前端·flutter
yqcoder1 小时前
突破性能瓶颈:深入理解 JavaScript TypedArray
java·开发语言·javascript
yqcoder1 小时前
JS 中的“空”之双雄:null vs undefined
开发语言·前端·javascript
计算机安禾1 小时前
【c++面向对象编程】第8篇:const成员与mutable:常对象与常函数
开发语言·javascript·c++
浩~~1 小时前
AI-Web 靶场
java·前端·网络