DOM 基础教程

第一部分:DOM 基础概念

1. 什么是 DOM?

  • 全称: Document Object Model (文档对象模型)。

  • 本质: 是浏览器将 HTML 或 XML 文档解析成的一个树形结构对象模型

  • 作用: 它提供了一套编程接口 (API),允许 JavaScript 等脚本语言动态地访问、修改、添加或删除网页的内容、结构和样式。

  • 关键理解:

    • 对象模型: 网页中的每个部分(元素、属性、文本、注释等)都被表示为一个对象

    • 树形结构: 这些对象按照 HTML 的嵌套层级关系组织成一棵“树”。树有根节点 (document)、父节点、子节点、兄弟节点。

    • 接口: DOM 定义了一堆属性 (Properties) 和 方法 (Methods),让我们可以通过 JavaScript 操作这些对象。

2. DOM 树结构 (节点树)

  • document 节点: 是整个 DOM 树的根节点。它代表整个 HTML 文档。所有其他节点都是它的后代。

  • 元素节点 (Element Nodes): 对应 HTML 中的标签 (<html><div><p><img><a> 等)。它们是树的主要分支。

  • 属性节点 (Attribute Nodes): 依附于元素节点,表示元素的属性 (idclasssrchrefstyle 等)。(注意:在现代 DOM 操作中,属性通常作为元素节点的属性直接访问,较少单独操作属性节点)

  • 文本节点 (Text Nodes): 包含元素内的纯文本内容。例如 <p> 标签内的文字就是一个文本节点。

  • 注释节点 (Comment Nodes): 对应 HTML 中的注释 <!-- 这是一个注释 -->

  • 节点关系 (Relationships):

    • 父节点 (Parent Node): 直接包含当前节点的节点。<body> 是 <div> 的父节点。

    • 子节点 (Child Node): 被当前节点直接包含的节点。<div> 是 <body> 的子节点。

    • 兄弟节点 (Sibling Node): 拥有同一个父节点的节点。两个相邻的 <p> 标签互为兄弟节点。

    • 后代节点 (Descendant Node): 当前节点包含的所有层级的节点(子节点、孙节点等)。

    • 祖先节点 (Ancestor Node): 包含当前节点的所有上层节点(父节点、祖父节点等,直到 document)。

可视化示例:

<!DOCTYPE html>
<html>
<head>
    <title>我的页面</title>
</head>
<body>
    <h1 id="main-title">欢迎!</h1> <!-- 元素节点 (id 是它的属性) -->
    <div class="content">
        <p>这是一个段落。</p> <!-- 元素节点 -->
        <p>这是另一个<span>段落</span>。</p> <!-- 元素节点,内含文本节点和另一个元素节点 -->
        <img src="image.jpg" alt="图片"> <!-- 元素节点 (src 和 alt 是属性) -->
    </div>
</body>
</html>
 

对应的简化 DOM 树:

document
├── html (Element)
    ├── head (Element)
    │   └── title (Element)
    │       └── "我的页面" (Text)
    └── body (Element)
        ├── h1 (Element, id="main-title")
        │   └── "欢迎!" (Text)
        └── div (Element, class="content")
            ├── p (Element)
            │   └── "这是一个段落。" (Text)
            ├── p (Element)
            │   ├── "这是另一个" (Text)
            │   ├── span (Element)
            │   │   └── "段落" (Text)
            │   └── "。" (Text)
            └── img (Element, src="image.jpg", alt="图片")

第二部分:定位元素 (获取 DOM 节点)

JavaScript 需要通过 document 对象或其子节点开始,使用特定的方法“找到”你想操作的元素节点。

1. 主要方法

  • document.getElementById('idName’)

    • 作用: 根据元素的唯一 id 属性查找单个元素。

    • 参数: 元素的 id 值 (字符串)。

    • 返回值: 匹配到的单个元素节点对象。如果没找到,返回 null

    • 示例: const titleElement = document.getElementById('main-title'); // 获取上面例子中的 h1

  • document.querySelector('cssSelector')

    • 作用: 使用 CSS 选择器语法查找匹配的第一个元素。

    • 参数: 一个有效的 CSS 选择器字符串 (如 '.classname''#idname''div p''input[type="text"]')。

    • 返回值: 匹配到的第一个元素节点对象。如果没找到,返回 null

    • 示例:

      const firstParagraph = document.querySelector('p'); // 获取文档中第一个 <p>
      const contentDiv = document.querySelector('.content'); // 获取 class 为 'content' 的第一个元素
      const spanInSecondP = document.querySelector('p:nth-child(2) span'); // 获取第二个 p 里的 span

  • document.querySelectorAll('cssSelector')

    • 作用: 使用 CSS 选择器语法查找匹配的所有元素。

    • 参数: 一个有效的 CSS 选择器字符串。

    • 返回值: 一个 NodeList 对象。它是一个类似数组的集合,包含了所有匹配到的元素节点。如果没找到,返回一个空的 NodeList(注意:NodeList 不是真正的 JavaScript 数组,但它通常有 length 属性和可以通过索引 [index] 访问元素,以及 forEach 方法。如果需要数组方法,可以用 Array.from(nodeList) 转换)

    • 示例:

      const allParagraphs = document.querySelectorAll('p'); // 获取文档中所有 <p>
      const imagesInContent = document.querySelectorAll('.content img'); // 获取 .content 里的所有 img
       
  • document.getElementsByClassName('className')

    • 作用: 根据元素的 class 属性查找所有匹配的元素。

    • 参数: 元素的 class 值 (字符串)。可以包含多个类名,用空格分隔(匹配包含所有这些类的元素)。

    • 返回值: 一个 HTMLCollection 对象。它也是一个类似数组的、实时 (live) 的集合,包含了所有匹配的元素节点。元素在文档中的变化(添加/删除)会反映在这个集合中。如果没找到,返回一个空的 HTMLCollection

    • 示例: const contentElements = document.getElementsByClassName('content'); // 获取所有 class 包含 'content' 的元素

  • document.getElementsByTagName('tagName')

    • 作用: 根据 HTML 标签名查找所有匹配的元素。

    • 参数: 标签名 (字符串, 如 'div''p''img')。

    • 返回值: 一个 HTMLCollection 对象 (实时集合),包含了所有匹配的元素节点。

    • 示例: const allDivs = document.getElementsByTagName('div'); // 获取所有 <div> 元素

2. 通过节点关系定位

一旦你获得了一个节点 (elementNode),你可以通过它的属性访问其相关节点:

  • elementNode.parentNode / elementNode.parentElement: 获取父节点(通常是元素节点)。

  • elementNode.childNodes: 获取一个包含所有直接子节点的 NodeList (包括元素节点、文本节点、注释节点等)。(注意:换行和空格也会被当作文本节点!)

  • elementNode.children: 获取一个包含所有直接子元素节点的 HTMLCollection (只包含元素节点,忽略文本/注释节点)。通常更常用。

  • elementNode.firstChild / elementNode.firstElementChild: 获取第一个子节点 / 第一个子元素节点。

  • elementNode.lastChild / elementNode.lastElementChild: 获取最后一个子节点 / 最后一个子元素节点。

  • elementNode.previousSibling / elementNode.previousElementSibling: 获取前一个兄弟节点 / 前一个兄弟元素节点。

  • elementNode.nextSibling / elementNode.nextElementSibling: 获取下一个兄弟节点 / 下一个兄弟元素节点。

示例:

const contentDiv = document.querySelector('.content');
const firstChildElement = contentDiv.firstElementChild; // 第一个子元素 (第一个 <p>)
const lastChildElement = contentDiv.lastElementChild; // 最后一个子元素 (<img>)
const parentOfContent = contentDiv.parentElement; // <body>
const secondParagraph = firstChildElement.nextElementSibling; // 第二个 <p> (假设 firstChildElement 是第一个 p)
 

第三部分:操作元素内容、属性和样式

1. 操作元素内容

  • elementNode.innerHTML

    • 作用: 获取或设置元素内部的 HTML 字符串。设置时会解析字符串中的 HTML 标签。

    • 用途: 需要插入包含 HTML 标签的内容时使用(如从服务器加载一段富文本)。⚠️ 警告: 直接使用用户输入设置 innerHTML 可能导致 XSS 跨站脚本攻击。务必对用户输入进行严格过滤和转义!

    • 示例:

      const div = document.querySelector('div');
      console.log(div.innerHTML); // 输出: "<p>这是一个段落。</p><p>这是另一个<span>段落</span>。</p><img src="image.jpg" alt="图片">"
      div.innerHTML = '<h2>新标题</h2><p>新内容...</p>'; // 完全替换 div 内的内容
      div.innerHTML += '<p>追加的内容</p>'; // 在现有内容后追加
       
  • elementNode.textContent

    • 作用: 获取或设置元素及其所有后代的纯文本内容。设置时,传入的任何字符串都会被当作纯文本处理,HTML 标签不会被解析。

    • 用途: 当你只需要读写文本内容,且不希望插入或解析 HTML 标签时使用。更安全(避免 XSS)。

    • 示例:

      const title = document.getElementById('main-title');
      console.log(title.textContent); // 输出: "欢迎!"
      title.textContent = '你好,世界!'; // 将 h1 的文本改为 "你好,世界!"
       
  • elementNode.innerText

    • 作用: 获取或设置元素的“渲染”文本内容。它会考虑 CSS 样式(如 display: none 的元素内容不会被包含),并且会触发回流(reflow)来计算样式。结果通常与用户看到的一致。

    • 区别 vs textContent innerText 受 CSS 影响,textContent 直接获取 DOM 节点中的文本(包括隐藏元素的文本)。innerText 性能稍差(因为需要计算样式)。通常优先使用 textContent,除非你需要精确匹配视觉呈现。

2. 操作元素属性

HTML 元素的属性(idclasssrchreftitlealtdata-* 等)可以通过 DOM 对象的属性或方法访问。

  • 直接通过 DOM 对象属性访问 (最常用):

    • DOM 元素对象通常将标准的 HTML 属性映射为其自身的属性。

    • 示例:

      const myImage = document.querySelector('img');
      console.log(myImage.src); // 获取 src 属性值 (完整 URL)
      console.log(myImage.alt); // 获取 alt 属性值
      myImage.src = 'new-image.png'; // 设置 src 属性
      myImage.title = '新图片标题'; // 设置 title 属性
      const myLink = document.querySelector('a');
      console.log(myLink.href); // 获取 href (完整 URL)
      myLink.href = 'https://www.newexample.com'; // 设置 href
       
  • elementNode.getAttribute('attrName')

    • 作用: 获取元素指定属性的值。

    • 参数: 属性名 (字符串)。

    • 返回值: 属性值的字符串。如果属性不存在,返回 null

    • 用途: 获取非标准属性(如 data-* 自定义属性)的值,或者需要确保获取的是原始字符串(如 href 属性直接访问会返回绝对 URL,getAttribute('href') 可能返回相对路径)。

    • 示例: const dataValue = element.getAttribute('data-user-id');

  • elementNode.setAttribute('attrName', 'attrValue')

    • 作用: 设置元素指定属性的值。如果属性不存在,则创建它。

    • 参数: 属性名 (字符串), 属性值 (字符串)。

    • 示例: element.setAttribute('data-role', 'admin');

  • elementNode.hasAttribute('attrName')

    • 作用: 检查元素是否拥有指定的属性。

    • 参数: 属性名 (字符串)。

    • 返回值: true (存在) 或 false (不存在)。

    • 示例: if (element.hasAttribute('required')) { ... }

  • elementNode.removeAttribute('attrName')

    • 作用: 从元素中移除指定的属性。

    • 参数: 属性名 (字符串)。

    • 示例: element.removeAttribute('disabled');

  • 操作 class 属性 (特殊且常用):

    • elementNode.className:获取或设置元素的整个 class 属性的字符串值(所有类名,空格分隔)。

      const btn = document.querySelector('button');
      btn.classList.add('btn-primary', 'btn-lg'); // 添加两个类
      btn.classList.remove('btn-default'); // 移除一个类
      btn.classList.toggle('active'); // 切换 'active' 类
      if (btn.classList.contains('disabled')) { ... } // 检查是否包含
       
    • 更现代、更强大的方式: elementNode.classList 属性。它返回一个 DOMTokenList 对象,提供方便的方法操作类名:

      • classList.add('className1', 'className2', ...):添加一个或多个类名。

      • classList.remove('className1', 'className2', ...):移除一个或多个类名。

      • classList.toggle('className', [force]):如果类名存在则删除它,不存在则添加它。force 是可选布尔值,true 表示强制添加,false 表示强制移除。

      • classList.contains('className'):检查元素是否包含指定的类名,返回 true 或 false

      • classList.replace('oldClass', 'newClass'):替换一个类名。

      • 示例:

        const btn = document.querySelector('button');
        btn.classList.add('btn-primary', 'btn-lg'); // 添加两个类
        btn.classList.remove('btn-default'); // 移除一个类
        btn.classList.toggle('active'); // 切换 'active' 类
        if (btn.classList.contains('disabled')) { ... } // 检查是否包含
         

3. 操作元素样式 (style 属性)

  • elementNode.style 属性:

    • 作用: 获取或设置元素的内联样式 (即写在 HTML 标签 style 属性里的样式)。

    • 访问方式: element.style.cssPropertyName。CSS 属性名需要使用驼峰式 (camelCase) 写法(因为 JavaScript 中 - 是操作符):

      • CSS: background-color -> JS: element.style.backgroundColor

      • CSS: font-size -> JS: element.style.fontSize

      • CSS: z-index -> JS: element.style.zIndex

    • 设置值: 值必须是字符串,并且通常需要包含单位(如 pxem%)。

    • 示例:

      const box = document.getElementById('myBox');
      box.style.backgroundColor = 'blue'; // 设置背景色
      box.style.width = '200px'; // 设置宽度
      box.style.padding = '10px 15px'; // 设置内边距
      console.log(box.style.color); // 获取当前内联样式的 color 值 (如果没有设置过内联样式,可能返回空字符串)
       
    • 注意: element.style 只能获取和设置内联样式。它无法获取通过 CSS 样式表设置的样式。要获取计算后的样式(最终应用到元素上的所有样式),需要使用 window.getComputedStyle(element)

  • 批量修改样式: 修改 style.cssText 属性可以一次性设置多个内联样式。

    element.style.cssText = 'width: 100px; height: 100px; background: red;';
    // 等同于
    element.setAttribute('style', 'width: 100px; height: 100px; background: red;');
     
  • 最佳实践: 对于复杂的样式修改,通常优先考虑通过修改 class 来应用预定义在 CSS 中的样式,而不是直接操作大量 style 属性。这样更易于维护,且可以利用 CSS 的层叠和继承特性。


第四部分:创建、添加、删除和替换元素

1. 创建新节点

  • document.createElement('tagName')

    • 作用: 创建一个新的元素节点

    • 参数: HTML 标签名 (字符串, 如 'div''p''li')。

    • 返回值: 新创建的元素节点对象。此时该元素仅存在于内存中,尚未添加到 DOM 树里。

    • 示例: const newParagraph = document.createElement('p');

  • document.createTextNode('text')

    • 作用: 创建一个新的文本节点

    • 参数: 文本内容 (字符串)。

    • 返回值: 新创建的文本节点对象。

    • 示例: const newText = document.createTextNode('这是新创建的文本内容。');

2. 添加节点到 DOM 树

创建好的节点需要添加到 DOM 树中的特定位置才能显示在页面上。

  • parentNode.appendChild(newNode)

    • 作用: 将 newNode 添加为 parentNode 的最后一个子节点

    • 参数: 要添加的节点对象。

    • 示例:

      const newDiv = document.createElement('div');
      newDiv.textContent = '我是新的 div!';
      document.body.appendChild(newDiv); // 添加到 <body> 的末尾
       
  • parentNode.insertBefore(newNode, referenceNode)

    • 作用: 将 newNode 插入到 parentNode 的子节点 referenceNode 之前

    • 参数: 要添加的节点对象 (newNode), 参考节点对象 (referenceNode)。referenceNode 必须是 parentNode 的子节点。

    • 示例:

      const newItem = document.createElement('li');
      newItem.textContent = '新列表项';
      const list = document.getElementById('myList');
      const firstItem = list.firstElementChild;
      list.insertBefore(newItem, firstItem); // 在第一个列表项之前插入新项
       
  • elementNode.insertAdjacentHTML(position, htmlString)

    • 作用: 将指定的 HTML 文本字符串解析为节点,并插入到相对于 elementNode 的指定位置。非常高效便捷。

    • 参数:

      • position (字符串): 指定插入位置,必须是以下之一:

        • 'beforebegin': 在 elementNode 本身之前(作为前一个兄弟节点)。

        • 'afterbegin': 在 elementNode 内部的第一个子节点之前。

        • 'beforeend': 在 elementNode 内部的最后一个子节点之后。(最常用,类似 appendChild)

        • 'afterend': 在 elementNode 本身之后(作为下一个兄弟节点)。

      • htmlString (字符串): 要解析并插入的 HTML 字符串。

    • 示例:

      const refElement = document.querySelector('.reference');
      refElement.insertAdjacentHTML('beforebegin', '<p>插入在参考元素之前</p>');
      refElement.insertAdjacentHTML('afterbegin', '<span>插入在参考元素内部的开头</span>');
      refElement.insertAdjacentHTML('beforeend', '<span>插入在参考元素内部的末尾</span>'); // 常用!
      refElement.insertAdjacentHTML('afterend', '<p>插入在参考元素之后</p>');
       

3. 移除节点

  • parentNode.removeChild(childNode)

    • 作用: 从 parentNode 的子节点列表中移除指定的 childNode

    • 参数: 要移除的子节点对象。

    • 返回值: 被移除的节点。这个节点仍然存在于内存中,可以稍后添加到其他地方。

    • 示例:

      const list = document.getElementById('myList');
      const itemToRemove = list.children[1]; // 假设移除第二个子元素
      list.removeChild(itemToRemove);
       
  • elementNode.remove() (较新,更简洁)

    • 作用: 直接从其父节点中移除自身。

    • 示例: itemToRemove.remove(); // 效果同上

4. 替换节点

  • parentNode.replaceChild(newNode, oldNode)

    • 作用: 用 newNode 替换 parentNode 的子节点 oldNode

    • 参数: 新节点 (newNode), 要被替换的旧节点 (oldNode)。oldNode 必须是 parentNode 的子节点。

    • 返回值: 被替换的旧节点 (oldNode)。

    • 示例:

      const oldHeading = document.querySelector('h2');
      const newHeading = document.createElement('h3');
      newHeading.textContent = '新标题';
      oldHeading.parentNode.replaceChild(newHeading, oldHeading);
       

第五部分:事件处理 - 响应用户交互

DOM 事件是用户或浏览器与网页交互时发生的动作(如点击、鼠标移动、键盘按键、页面加载、输入框改变等)。JavaScript 可以“监听”这些事件,并在事件发生时执行特定的代码(事件处理函数)。

1. 事件监听 (注册事件处理程序)

有三种主要方式为元素添加事件监听器:

  • HTML 属性事件处理程序 (不推荐):

    <button onclick="alert('按钮被点击了!')">点我</button>
    • 或调用外部函数:

      
      
      <button onclick="handleClick()">点击我</button>
      <script>
        function handleClick() {
          alert("静态注册成功!");
        }
      </script>
    • 缺点: HTML 和 JavaScript 代码紧密耦合,难以维护,且只能添加一个处理程序。

  • DOM 属性事件处理程序:

    const button = document.querySelector('button');
    button.onclick = function() {
        alert('按钮被点击了!');
    };
    • 缺点: 一个事件类型只能绑定一个处理函数。后绑定的会覆盖前面的。

  • addEventListener() (推荐!)

    • 语法: elementNode.addEventListener(eventType, listenerFunction, [options])

    • 参数:

      • eventType (字符串): 事件类型名称 (如 'click''mouseover''keydown''submit''change''load')。不加 on 前缀!

      • listenerFunction (函数): 当事件发生时被调用的函数。这个函数会接收到一个 Event 对象作为参数,该对象包含事件相关信息。

      • options (可选对象或布尔值):

        • 布尔值:表示事件是否在捕获阶段触发(默认为 false,在冒泡阶段触发)。(见下方事件流)

        • 对象:常用属性 { once: true } (只触发一次后自动移除监听器), { passive: true } (向浏览器承诺处理函数不会调用 preventDefault(),用于优化滚动等性能), capture (同布尔值)。

    • 优点: 可以为同一个元素的同一个事件类型添加多个处理函数,不会覆盖。提供了更细粒度的控制(捕获/冒泡)。可以使用 removeEventListener() 精确移除。

    • 示例:

      const button = document.querySelector('button');
      function handleClick(event) {
          console.log('按钮被点击了!', event);
          // event.target 指向实际触发事件的元素 (可能是 button 或其子元素)
          // event.currentTarget 指向当前正在处理事件的元素 (即绑定了监听器的 button)
      }
      button.addEventListener('click', handleClick);
      // 可以添加另一个处理函数
      button.addEventListener('click', function() { console.log('第二个处理函数'); });

2. 事件对象 (Event)

当事件处理函数被调用时,浏览器会自动传入一个 Event 对象(或其子类型对象,如 MouseEventKeyboardEvent)作为参数。这个对象包含大量与事件相关的信息:

  • 常用属性和方法:

    • event.target触发事件的最深层元素(事件实际发生的源头)。

    • event.currentTarget绑定事件监听器的元素(即 addEventListener 被调用的元素)。在处理函数内部,this 通常也指向 event.currentTarget

    • event.type: 事件的类型 (如 'click')。

    • event.preventDefault(): 阻止事件的默认行为。例如,阻止链接跳转 (<a> 的 click 事件)、阻止表单提交 (<form> 的 submit 事件)。

    • event.stopPropagation()阻止事件冒泡。防止事件继续向父元素传播。

    • event.stopImmediatePropagation(): 阻止事件冒泡,并且阻止同一个元素上绑定的其他同类型事件处理函数被执行。

    • 鼠标事件 (MouseEvent) 特有:

      • event.clientX / event.clientY: 鼠标指针相对于浏览器视口 (viewport) 的 X/Y 坐标。

      • event.pageX / event.pageY: 鼠标指针相对于整个文档的 X/Y 坐标(包含滚动偏移)。

      • event.button: 被按下的鼠标按钮 (0=左键, 1=中键, 2=右键)。

    • 键盘事件 (KeyboardEvent) 特有:

      • event.key: 按下的键的字符串表示 (如 'a''Enter''ArrowUp''Escape')。

      • event.code: 按下的键的物理按键代码 (如 'KeyA''Enter''ArrowUp''Escape')。布局无关。

      • event.ctrlKey / event.shiftKey / event.altKey / event.metaKey (Mac 的 Command 键): 布尔值,表示这些修饰键是否在事件发生时被按住。

3. 事件流:捕获与冒泡 (Event Flow)

当事件发生在具有父元素的元素上时,现代浏览器的事件传播分为三个阶段:

  1. 捕获阶段 (Capturing Phase): 事件从 window 对象向下传播,经过 document<html><body>,层层传递直到实际触发事件的目标元素 (target)(默认情况下,addEventListener 在此阶段不触发处理函数,除非将 options 设为 true 或 { capture: true })

  2. 目标阶段 (Target Phase): 事件到达目标元素 target 本身。在此阶段,绑定在 target 上的处理函数(无论是否捕获)都会触发。

  3. 冒泡阶段 (Bubbling Phase): 事件从目标元素 target 向上冒泡传播,经过其父元素、祖父元素,层层向上直到 window 对象。(默认情况下,addEventListener 在此阶段触发处理函数)

理解冒泡: 如果点击了一个 <button>(在 <div> 内):

  • 捕获阶段:window -> document -> <html> -> <body> -> <div> (如果这些元素有在捕获阶段的监听器)

  • 目标阶段:<button> 上的监听器触发。

  • 冒泡阶段:<div> -> <body> -> <html> -> document -> window (如果这些元素有在冒泡阶段的监听器)

event.stopPropagation() 的作用就是阻止事件在捕获或冒泡阶段继续传播。

4. 事件委托 (Event Delegation)

利用事件冒泡机制,我们可以将事件监听器绑定在一个公共的父元素上,而不是为每个子元素单独绑定。当事件在子元素上触发并冒泡到父元素时,父元素的监听器会根据 event.target 来判断是哪个子元素触发的事件,并执行相应的操作。

优点:

  • 性能优化: 减少事件监听器的数量,尤其对大量动态生成的子元素非常高效。

  • 简化代码: 无需为每个新添加的子元素手动绑定事件。

  • 处理动态元素: 对后来添加的子元素自动生效。

示例: 给一个 <ul> 下的所有 <li> 添加点击事件。

const list = document.getElementById('myList');
list.addEventListener('click', function(event) {
    // 检查点击的目标 (event.target) 是否是 <li> 元素 (或者我们需要委托的元素)
    if (event.target.tagName === 'LI') { // 注意 tagName 是大写的
        console.log('你点击了列表项:', event.target.textContent);
        // 在这里执行针对被点击的 <li> 的操作
    }
    // 如果列表项里还有子元素 (比如 <span>),event.target 可能是那个子元素。
    // 如果需要精确委托到 <li>,可能需要向上遍历找到最近的 <li> 父元素
    // let clickedLi = event.target.closest('li');
    // if (clickedLi) { ... }
});
 

5. 移除事件监听器

  • removeEventListener(eventType, listenerFunction, [options])

    • 作用: 移除之前使用 addEventListener() 添加的事件监听器。

    • 参数: 必须与添加时使用的 eventTypelistenerFunction 函数引用和 options(如果添加时指定了)完全一致

    • 关键: listenerFunction 必须是同一个函数对象(不能是匿名函数,除非你保存了引用)。

    • 示例:

      function handleClick() { console.log('Clicked'); }
      button.addEventListener('click', handleClick);
      // 稍后移除
      button.removeEventListener('click', handleClick);
       

第六部分:其他重要概念和技巧

1. window.onload vs DOMContentLoaded

  • window.onload / window.addEventListener('load', ...): 当整个页面(包括 HTML、CSS、JavaScript、图片、iframe 等所有资源)完全加载完毕后触发。

  • document.addEventListener('DOMContentLoaded', ...): 当 HTML 文档被完全解析和加载完成(DOM 树构建完毕),无需等待样式表、图像和子框架完全加载时触发。

  • 选择: 如果你的脚本只需要操作 DOM,不依赖外部资源(如图片尺寸),使用 DOMContentLoaded 可以让用户更早地与页面交互。如果需要确保所有资源(特别是图片)都加载完成,则使用 load

2. 操作表单元素 (<input><select><textarea>)

表单元素有其特定的属性和方法:

  • 获取/设置值:

    • 文本输入框 (<input type="text"><textarea>): element.value

    • 单选/复选框 (<input type="radio"><input type="checkbox">): element.checked (布尔值)

    • 下拉选择框 (<select>):

      • element.value: 获取选中的 <option> 的 value 属性。

      • element.selectedIndex: 获取选中选项的索引。

      • element.options[element.selectedIndex].text: 获取选中选项的显示文本。

  • 监听变化: 使用 'change' 事件(值改变且元素失去焦点时触发)或 'input' 事件(值每次改变时实时触发)。

3. data-* 自定义属性

  • HTML5 允许使用 data- 前缀定义自定义属性来存储私有数据。

  • 访问方式:

    • elementNode.dataset.propertyName: 通过元素的 dataset 属性访问。属性名需要去掉 data- 前缀,并使用驼峰式访问。

      • HTML: <div data-user-id="123" data-role="admin"></div>

      • JS: element.dataset.userId (值为 '123'), element.dataset.role (值为 'admin')

    • elementNode.getAttribute('data-attr-name'): 直接获取属性值字符串。

4. 性能注意事项

  • 减少 DOM 访问: 访问 DOM(尤其是查询)相对较慢。尽量避免在循环中重复查询相同的元素,将结果缓存到变量中。

  • 批量修改: 如果需要多次修改 DOM(如添加多个子元素),尽量先将这些修改在内存中完成(使用 DocumentFragment 或临时父元素),然后一次性添加到 DOM 树中,减少回流次数。

  • 事件委托: 如前所述,是优化事件处理的利器。

  • 避免强制同步布局 (Forced Synchronous Layout): 连续进行读取布局属性(如 offsetWidthscrollTop)和修改样式的操作时,浏览器可能会为了提供准确的读取值而进行多次不必要的布局计算。尽量将读取操作集中在一起,修改操作集中在一起。


第七部分:实战小练习

让我们用一个小例子整合所学知识:

目标: 创建一个待办事项列表 (To-Do List)。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>简易待办事项</title>
    <style>
        #todo-list { list-style: none; padding: 0; }
        #todo-list li { margin: 5px 0; padding: 5px; background: #eee; }
        #todo-list li.done { text-decoration: line-through; color: #999; }
        .delete-btn { float: right; cursor: pointer; }
    </style>
</head>
<body>
    <h1>我的待办事项</h1>
    <input type="text" id="new-todo" placeholder="输入新任务...">
    <button id="add-btn">添加</button>
    <ul id="todo-list"></ul>

    <script>
        // 1. 获取必要的 DOM 元素
        const newTodoInput = document.getElementById('new-todo');
        const addButton = document.getElementById('add-btn');
        const todoList = document.getElementById('todo-list');

        // 2. 添加新待办事项的函数
        function addTodoItem() {
            const todoText = newTodoInput.value.trim(); // 获取输入值并去除首尾空格
            if (todoText === '') return; // 如果为空则忽略

            // 3. 创建新的列表项元素 (<li>)
            const newTodoItem = document.createElement('li');

            // 4. 创建任务文本节点
            const todoTextNode = document.createTextNode(todoText);

            // 5. 创建删除按钮
            const deleteButton = document.createElement('span');
            deleteButton.textContent = '❌';
            deleteButton.className = 'delete-btn';
            deleteButton.addEventListener('click', function() {
                newTodoItem.remove(); // 点击删除按钮时移除该项
            });

            // 6. 将文本和删除按钮添加到 <li> 中
            newTodoItem.appendChild(todoTextNode);
            newTodoItem.appendChild(deleteButton);

            // 7. 为列表项本身添加点击事件 (标记完成/未完成)
            newTodoItem.addEventListener('click', function(event) {
                // 避免点击删除按钮时也触发这个事件 (利用事件委托思想检查target)
                if (event.target !== deleteButton) {
                    newTodoItem.classList.toggle('done'); // 切换 'done' 类
                }
            });

            // 8. 将新项添加到列表的末尾
            todoList.appendChild(newTodoItem);

            // 9. 清空输入框并聚焦
            newTodoInput.value = '';
            newTodoInput.focus();
        }

        // 10. 为"添加"按钮绑定点击事件
        addButton.addEventListener('click', addTodoItem);

        // 11. 为输入框绑定回车键事件 (keydown 或 keypress)
        newTodoInput.addEventListener('keydown', function(event) {
            if (event.key === 'Enter') { // 按下回车键
                addTodoItem();
            }
        });

        // 12. 页面加载后让输入框自动聚焦 (可选)
        window.addEventListener('DOMContentLoaded', function() {
            newTodoInput.focus();
        });
    </script>
</body>
</html>
 

这个例子涵盖了:

  • 获取元素 (getElementById)

  • 创建元素 (createElement) 和文本节点 (createTextNode)

  • 操作元素内容 (textContentappendChild)

  • 操作类名 (classList.addclassList.toggle)

  • 添加事件监听 (addEventListener):按钮点击、列表项点击、删除按钮点击、输入框回车键

  • 事件对象 (event.keyevent.target)

  • 移除节点 (remove())

  • 表单输入处理 (value)

  • DOMContentLoaded 事件


总结

  1. DOM 是什么: 理解 DOM 是浏览器创建的、代表文档结构的树形对象模型。

  2. 定位元素: 熟练使用 getElementByIdquerySelectorquerySelectorAllgetElementsByClassNamegetElementsByTagName 以及节点关系属性查找元素。

  3. 操作内容/属性/样式: 使用 innerHTMLtextContentstyleclassListgetAttributesetAttribute 等修改元素。

  4. 增删改节点: 使用 createElementcreateTextNodeappendChildinsertBeforeinsertAdjacentHTMLremoveChild/remove()replaceChild 动态改变 DOM 结构。

  5. 事件处理: 理解事件流(捕获、目标、冒泡),使用 addEventListener 注册事件,使用 Event 对象,掌握事件委托技巧,使用 removeEventListener 移除事件。

  6. 其他要点: window.onload vs DOMContentLoaded, 表单操作, data-* 属性, 性能意识。

持续学习建议:

  • 实践!实践!实践! 构建小项目(如计算器、图片画廊、交互式表单验证)。

  • 查阅 MDN Web 文档: 这是学习 Web 技术最权威、最详细的资源 (文档对象模型(DOM) - Web API | MDN)。

  • 学习浏览器开发者工具: 使用 Elements 面板查看和修改 DOM,使用 Console 面板测试代码,使用 Event Listeners 面板调试事件。

  • 探索更多 DOM API: 如 Element 的 closest()matches()getBoundingClientRect() 等方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值