宝宝版js教学(百万字)超详细 DOM操作

1.1 DOM 概述:网页的 “结构化数字地图”

当浏览器加载 HTML 页面时,会自动执行一个关键步骤 ——构建 DOM 树(Document Object Model Tree)。简单说,浏览器会把 HTML 文档中的每一个组成部分(标签、属性、文本、注释、甚至整个文档本身)都转换成一个个 “对象”,这些对象按照 HTML 的层级结构串联起来,形成一棵类似 “家族树” 的结构,这就是 DOM 树。

DOM 树的核心作用,是为 JavaScript 提供一套操控网页的 “接口”。因为 HTML 本身是静态的文本,JavaScript 无法直接修改文本内容,但通过 DOM 树,JS 可以像 “摆弄积木” 一样,精准定位到树中的任意一个 “节点对象”,进而实现:

  • 修改节点内容(比如把 “按钮” 改成 “点击我”);
  • 修改节点样式(比如把红色 div 改成蓝色);
  • 新增节点(比如点击按钮后加一个列表项);
  • 删除节点(比如删除过期的公告文本);
  • 绑定事件(比如点击按钮弹出提示框)。

一句话总结:HTML 是网页的 “骨架”,DOM 是骨架的 “数字镜像”,JavaScript 通过操作这个镜像,实现网页的动态效果

1.2 DOM 树的层级结构(实例拆解)

以一段简单的 HTML 代码为例,看 DOM 树如何构建:

html
复制代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <title>DOM示例</title> </head> <body> <div id="box" class="container"> <p>Hello DOM!</p> <button>点击</button> </div> </body> </html>

对应的 DOM 树结构如下(核心节点标注):

plaintext
复制代码
文档节点(document)—— 根节点,唯一 └── 元素节点(html)—— 根元素 ├── 元素节点(head) │ └── 元素节点(title) │ └── 文本节点("DOM示例")—— title的子节点 └── 元素节点(body) └── 元素节点(div)—— id="box"、class="container"(属性节点依附于此) ├── 元素节点(p) │ └── 文本节点("Hello DOM!")—— p的子节点 └── 元素节点(button) └── 文本节点("点击")—— button的子节点

从结构能清晰看到:

  • 层级关系:父节点(如bodydiv的父节点)、子节点(如divpbutton的父节点)、兄弟节点(如pbutton是兄弟节点);
  • 节点类型:不同节点各司其职,其中元素节点、文本节点、文档节点是开发中最常用的三类。

二、DOM 节点详解:网页的 “积木零件”

2.1 节点的分类(4 类核心,附实例)

DOM 树中的节点并非 “千篇一律”,根据功能和类型,主要分为 4 类,每类都有明确的职责和特征:

节点类型描述实例(对应上面 HTML)核心特征
文档节点(Document)代表整个 HTML 文档,是 DOM 树的 “根节点”,所有其他节点都是它的后代document对象(无需创建,直接使用)唯一的根节点,nodeType=9
元素节点(Element)代表 HTML 中的标签(如<div><p><button>),是 DOM 操作的核心对象<div id="box"><button>可包含子节点(文本 / 其他元素),nodeType=1
属性节点(Attribute)代表元素的属性(如idclasshref),依附于元素节点存在id="box"class="container"不是元素的子节点,是元素的 “属性集合”,nodeType=2
文本节点(Text)代表元素中的纯文本内容(不含 HTML 标签),通常是元素节点的子节点<p>中的 “Hello DOM!”、<button>中的 “点击”仅包含文本,无法再包含子节点,nodeType=3

2.2 节点的核心属性(3 个必记,附实战)

无论哪种节点,都包含 3 个基础属性,用于识别节点类型、名称和值,是区分节点的关键:

属性名作用不同节点的取值示例
nodeName返回节点的名称(只读)文档节点:#document;元素节点:标签名大写(如DIVBUTTON);文本节点:#text
nodeType返回节点的类型(用数字表示,只读),是判断节点类型最可靠的方式文档节点:9;元素节点:1;属性节点:2;文本节点:3
nodeValue返回节点的值(可读写,不同节点取值不同)文档节点:null;元素节点:null;属性节点:属性值(如"box");文本节点:文本内容(如"Hello DOM!"

实战代码(获取节点属性)

html
复制代码
<div id="box">Hello DOM!</div> <script> // 1. 获取元素节点(div) var box = document.getElementById("box"); // 2. 获取元素节点的属性 console.log("元素节点 - nodeName:", box.nodeName); // 输出 "DIV"(大写标签名) console.log("元素节点 - nodeType:", box.nodeType); // 输出 1(元素节点标识) console.log("元素节点 - nodeValue:", box.nodeValue); // 输出 null(元素节点无值) // 3. 获取文本节点(div的第一个子节点) var textNode = box.firstChild; console.log("文本节点 - nodeName:", textNode.nodeName); // 输出 "#text" console.log("文本节点 - nodeType:", textNode.nodeType); // 输出 3(文本节点标识) console.log("文本节点 - nodeValue:", textNode.nodeValue); // 输出 "Hello DOM!"(文本内容) // 4. 获取文档节点属性 console.log("文档节点 - nodeName:", document.nodeName); // 输出 "#document" console.log("文档节点 - nodeType:", document.nodeType); // 输出 9(文档节点标识) </script>

2.3 常用节点的特殊说明

2.3.1 文档节点(Document)

  • 地位:DOM 树的 “根”,所有操作都从document开始;
  • 访问方式:无需创建,直接使用document对象(浏览器内置);
  • 核心作用:提供查找、创建节点的方法(如getElementByIdcreateElement)。

2.3.2 元素节点(Element)

  • 地位:DOM 操作的 “核心主角”,几乎所有动态效果都围绕元素节点展开;
  • 访问方式:通过document的查找方法获取(如getElementByIdquerySelector);
  • 核心作用:承载属性、文本、子元素,是样式修改、事件绑定的主要对象。

2.3.3 属性节点(Attribute)

  • 注意点:属性节点不是元素节点的子节点,而是元素节点的 “属性集合” 中的一员;
  • 访问方式:无需单独获取属性节点,通过元素节点的getAttributesetAttribute方法操作属性值即可(开发中很少直接操作属性节点);
  • 示例:box.getAttribute("id")(获取id属性值),而非先获取属性节点再读值。

2.3.4 文本节点(Text)

  • 注意点:文本节点会包含元素标签内的所有空白字符(如换行、空格);
  • 访问方式:通过元素节点的firstChildlastChildchildNodes获取;
  • 示例:box.firstChild(获取div内的文本节点),box.firstChild.nodeValue(修改文本内容)。

三、DOM 核心操作:从查找、读取到修改(实战详解)

DOM 操作的核心逻辑可总结为 “三步曲”:查找节点 → 读取节点信息 → 修改节点(内容 / 样式 / 结构),下面分模块详细拆解,每个方法都附完整代码示例。

3.1 查找 HTML 元素:精准定位节点(5 种方法,附对比)

要操作节点,首先要 “找到它”。document提供了 5 种常用的查找方法,覆盖不同场景(按 id、标签名、类名、CSS 选择器):

方法名功能描述语法格式返回值类型优缺点对比
getElementById(id)按元素的id属性查找(id在页面中唯一)document.getElementById("id值")单个元素节点(找到返回元素,未找到返回null优点:速度最快、精准;缺点:只能按 id 查找
getElementsByTagName(tagName)按 HTML 标签名查找(如"div""button"document.getElementsByTagName("标签名")HTMLCollection(类数组,实时更新)优点:支持批量查找;缺点:只能按标签名查找,返回类数组需遍历
getElementsByClassName(className)按元素的class属性查找(支持多个类名,用空格分隔)document.getElementsByClassName("类名")HTMLCollection(类数组,实时更新)优点:按类名批量查找;缺点:IE8 及以下不支持
querySelector(selector)按 CSS 选择器查找(如.class#iddiv p),返回首个匹配元素document.querySelector("CSS选择器")单个元素节点(找到返回元素,未找到返回null优点:支持复杂选择器(如后代选择器);缺点:只返回第一个匹配元素
querySelectorAll(selector)按 CSS 选择器查找,返回所有匹配元素document.querySelectorAll("CSS选择器")NodeList(类数组,非实时更新)优点:支持复杂选择器,批量查找;缺点:IE8 及以下部分支持

3.1.1 实战代码:5 种查找方法示例

html
复制代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <title>DOM查找示例</title> </head> <body> <div id="container" class="main"> <p class="text">段落1</p> <p class="text">段落2</p> <button id="btn" class="btn">点击按钮</button> </div> <script> // 1. getElementById:按id找容器div var container = document.getElementById("container"); console.log("1. getElementById:", container); // 输出 <div id="container" class="main"> // 2. getElementsByTagName:按标签名找所有p标签 var pTags = document.getElementsByTagName("p"); console.log("2. getElementsByTagName(长度):", pTags.length); // 输出 2(2个p标签) console.log("2. 第一个p标签:", pTags[0]); // 输出 <p class="text">段落1</p> // 3. getElementsByClassName:按类名找所有.text元素 var textElements = document.getElementsByClassName("text"); console.log("3. getElementsByClassName(长度):", textElements.length); // 输出 2 // 4. querySelector:按CSS选择器找第一个.btn元素(按钮) var btn = document.querySelector(".main .btn"); console.log("4. querySelector:", btn); // 输出 <button id="btn" class="btn">点击按钮</button> // 5. querySelectorAll:按CSS选择器找所有.text元素 var allText = document.querySelectorAll(".container .text"); console.log("5. querySelectorAll(长度):", allText.length); // 输出 2 // 遍历NodeList(类数组) allText.forEach(function(item) { console.log("5. 遍历结果:", item.innerText); // 依次输出 "段落1"、"段落2" }); </script> </body> </html>

3.1.2 关键注意点

  • 类数组处理getElementsByTagNamegetElementsByClassName返回HTMLCollectionquerySelectorAll返回NodeList,两者都是 “类数组”(有长度、可通过索引访问),但:
    • HTMLCollection是 “实时更新” 的(页面元素变化时,集合会自动同步);
    • NodeList(除 IE 外)支持forEach遍历,HTMLCollection不支持,需用for循环遍历。
  • 查找范围:除了document,也可以通过 “父元素” 查找子元素(更精准,性能更好),例如:
javascript
复制代码
// 只在container内部找.btn元素,而非整个页面 var btnInContainer = container.querySelector(".btn");

3.2 获取 HTML 元素的值:读取节点信息(5 种方法,附对比)

找到元素节点后,常用需求是 “读取它的信息”(如文本内容、属性值、样式),5 种核心方法覆盖所有场景:

方法 / 属性功能描述适用场景示例代码(基于上面的 btn 元素)
元素.innerText读取元素的纯文本内容(忽略 HTML 标签,自动过滤换行 / 空格)只需获取文本,无需保留格式console.log(btn.innerText); // 输出 "点击按钮"
元素.innerHTML读取元素的HTML 内容(含标签,保留原始格式)需要获取元素内的 HTML 结构console.log(container.innerHTML); // 输出内部所有 HTML
元素。属性名读取元素的标准属性(如idclasshrefsrc),简化写法操作常用标准属性console.log(btn.id); // 输出 "btn"
元素.getAttribute (attribute)读取元素的任意属性(包括自定义属性,如data-index),通用写法操作自定义属性或不常用属性console.log(btn.getAttribute("class")); // 输出 "btn"
元素.style. 样式名读取元素的行内样式(写在style属性中的样式,需驼峰命名)读取行内样式(如widthbackgroundColorconsole.log(box.style.width); // 输出行内宽度值

3.2.1 实战代码:5 种读取方法示例

html
复制代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <title>DOM读取示例</title> <style> #box { width: 200px; /* 样式表中的样式,style属性读不到 */ } </style> </head> <body> <div id="box" class="container" style="height: 100px; background: red;" data-index="1"> <p>Hello <span>DOM</span>!</p> </div> <a href="https://www.baidu.com" id="link" data-type="external">百度一下</a> <script> var box = document.getElementById("box"); var link = document.getElementById("link"); // 1. innerText:读取纯文本(忽略span标签) console.log("1. innerText:", box.innerText); // 输出 "Hello DOM!" // 2. innerHTML:读取HTML内容(含span标签) console.log("2. innerHTML:", box.innerHTML); // 输出 "<p>Hello <span>DOM</span>!</p>" // 3. 元素.属性名:读取标准属性 console.log("3. 读取id属性:", box.id); // 输出 "box" console.log("3. 读取href属性:", link.href); // 输出 "https://www.baidu.com/"(完整URL) // 4. getAttribute:读取任意属性(含自定义属性) console.log("4. 读取class属性:", box.getAttribute("class")); // 输出 "container" console.log("4. 读取自定义属性data-index:", box.getAttribute("data-index")); // 输出 "1" console.log("4. 读取href属性(原始值):", link.getAttribute("href")); // 输出 "https://www.baidu.com" // 5. style.样式

3.2.2 关键对比与注意点

  • innerText vs innerHTML
    • innerText:只取纯文本,自动过滤 HTML 标签和多余空格 / 换行,适合只需读取文字的场景(如按钮文本、段落内容);
    • innerHTML:保留 HTML 标签和原始格式,适合需要获取元素内部结构的场景(如复制某块内容的 HTML 结构)。
  • 元素。属性名 vs getAttribute
    • 元素.属性名:读取标准属性时更简洁,但读取hrefsrc等属性会返回完整路径(如href会自动补全协议和域名);
    • getAttribute:读取属性的原始值,支持自定义属性(如data-*),兼容性更强,适合读取非标准属性的场景。
  • style. 样式名 vs getStyle
    • style.样式名:仅能读取行内样式(写在元素style属性中的样式),无法读取 CSS 样式表或<style>标签中的样式;
    • getStyle(自定义兼容方法):能读取元素当前应用的所有样式(无论来自行内、样式表还是默认值),但读取到的样式是只读的,无法通过该方法修改。

3.2.3 拓展:读取文本内容的兼容性方法

不同浏览器对文本节点的读取支持略有差异(如 IE8 及以下不支持textContent),可封装通用方法:

javascript
复制代码
// 通用获取任意元素文本内容的方法(兼容所有浏览器) function getInnerText(element) { // 判断浏览器是否支持textContent(主流浏览器) if (typeof element.textContent !== "undefined") { return element.textContent; // 支持则用textContent(与innerText功能类似,更标准) } else { return element.innerText; // 不支持则用innerText(IE8及以下) } } // 用法示例 console.log("通用方法读取文本:", getInnerText(box)); // 输出 "Hello DOM!"

3.3 改变 HTML 元素的值:修改节点信息(5 种方法,附实战)

读取信息后,更常用的是 “修改节点信息”(如改文本、改属性、改样式),方法与读取一一对应,支持直接赋值修改:

方法 / 属性功能描述适用场景示例代码(基于上面的 box 和 link 元素)
元素.innerText = 新文本修改元素的纯文本内容(覆盖原有内容,忽略 HTML 标签)快速修改文字,无需保留格式box.innerText = "新的文本内容";
元素.innerHTML = 新 HTML修改元素的 HTML 内容(覆盖原有内容,支持解析 HTML 标签)需要添加带标签的内容(如新增子元素)box.innerHTML = "<h3>新的标题</h3>";
元素。属性名 = 新值修改元素的标准属性(如idhrefsrc),简化写法快速修改常用标准属性link.href = "https://www.google.com";
元素.setAttribute (属性名,新值)修改元素的任意属性(含自定义属性),通用写法修改自定义属性或非标准属性box.setAttribute("data-index", "2");
元素.style. 样式名 = 新样式修改元素的行内样式(需驼峰命名,覆盖原有行内样式)动态修改元素样式(如颜色、大小)box.style.background = "blue";

3.3.1 实战代码:5 种修改方法示例

html
复制代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <title>DOM修改示例</title> <style> #box { width: 200px; margin: 10px 0; } </style> </head> <body> <div id="box" class="container" style="height: 100px; background: red;"> 原始文本 </div> <a href="https://www.baidu.com" id="link">百度一下</a> <script> var box = document.getElementById("box"); var link = document.getElementById("link"); // 1. innerText:修改纯文本 box.innerText = "修改后的纯文本(无标签)"; console.log("1. 修改后innerText:", box.innerText); // 输出 "修改后的纯文本(无标签)" // 2. innerHTML:修改HTML内容(支持标签解析) box.innerHTML = "<p>修改后的HTML内容<br><span style='color: white;'>带标签和样式</span></p>"; console.log("2. 修改后innerHTML:", box.innerHTML); // 输出带p和span标签的内容 // 3. 元素.属性名:修改标准属性 box.id = "new-box"; // 修改id属性 link.href = "https://www.google.com"; // 修改href属性 console.log("3. 修改后box的id:", box.id); // 输出 "new-box" console.log("3. 修改后link的href:", link.href); // 输出 "https://www.google.com/" // 4. setAttribute:修改任意属性(含自定义属性) box.setAttribute("class", "new-container"); // 修改class属性 box.setAttribute("data-index", "2"); // 修改自定义属性data-index link.setAttribute("target", "_blank"); // 添加新属性(打开新窗口) console.log("4. 修改后box的class:", box.getAttribute("class")); // 输出 "new-container" console.log("4. 修改后link的target:", link.getAttribute("target")); // 输出 "_blank" // 5. style.样式名:修改行内样式(驼峰命名) box.style.height = "150px"; // 修改高度 box.style.backgroundColor = "blue"; // 修改背景色(注意:background-color→backgroundColor) box.style.marginTop = "20px"; // 修改上外边距(margin-top→marginTop) console.log("5. 修改后box的行内样式:", box.style.cssText); // 输出所有行内样式 </script> </body> </html>

3.4 修改元素结构:给家里 “拆墙砌墙”

【梗版解析】

兄弟,如果你觉得家里格局不行 —— 比如抽屉太多想拆一个,或者想加个落地窗,DOM 这 5 个 “装修工具” 能帮你实现:

  1. createElement(砌新墙):想加个新抽屉(li),喊document.createElement("li"),新抽屉就造好了 —— 但此时还在仓库里(没显示在页面上),得搬回家;
  2. createTextNode(印字):给新抽屉贴个标签(文本),喊document.createTextNode("新抽屉"),标签就做好了,再用appendChild贴到抽屉上;
  3. appendChild(搬回家):把做好的抽屉(li)放进柜子(ul)里,喊ul.appendChild(li),抽屉立马显示在柜子最后面 —— 相当于把新买的家具摆进客厅;
  4. removeChild(拆旧墙):想拆旧抽屉,得先找到柜子(父元素)和旧抽屉(子元素),喊ul.removeChild(oldLi),旧抽屉直接被扔出门;
  5. replaceChild(换家具):把旧抽屉换成新抽屉,喊ul.replaceChild(newLi, oldLi),旧抽屉被扔,新抽屉直接摆在原来的位置;
  6. insertBefore(插家具):想把新抽屉插在第一个位置,喊ul.insertBefore(newLi, firstLi),新抽屉直接挤到最前面 —— 比装修队还快,秒插!

举个栗子:你想造个柜子(ul),加 4 个抽屉(li),步骤就是:先造柜子→循环造 4 个抽屉→每个抽屉贴标签→把抽屉全塞进柜子→把柜子摆进客厅(body)—— 一套操作下来,新家具就成型了!

【专业版解析】

修改元素结构是指 “新增、删除、替换、插入节点”,核心是操作 DOM 树的层级关系,6 个方法覆盖所有结构修改场景:

方法名功能描述语法格式关键注意点
document.createElement(tag)创建新元素节点(如<li><div>document.createElement("标签名")创建后需添加到 DOM 树(如appendChild)才会显示在页面
document.createTextNode(text)创建新文本节点(纯文本内容)document.createTextNode("文本内容")通常作为元素节点的子节点添加
父元素.appendChild (子元素)将子元素添加到父元素末尾(作为最后一个子节点)父元素.appendChild(子元素)子元素必须是节点对象(元素 / 文本节点)
父元素.removeChild (子元素)从父元素中删除指定子元素父元素.removeChild(子元素)必须通过父元素删除,不能直接删除子元素
父元素.replaceChild (新元素,旧元素)用新元素替换父元素中的旧元素(新元素占旧元素位置,旧元素被移除)父元素.replaceChild(新元素, 旧元素)新元素、旧元素必须是节点对象,且旧元素是父元素的子节点
父元素.insertBefore (新元素,参考元素)在父元素的参考元素之前插入新元素父元素.insertBefore(新元素, 参考元素)参考元素必须是父元素的子节点,若参考元素为null,则等同于appendChild

实战 1:动态创建列表(新手必练)

需求:创建一个 ul 列表,添加 4 个 li 子元素,显示在页面上。

html
复制代码
<div id="container"></div> <script> // 1. 获取容器(父元素) var container = document.getElementById("container"); // 2. 创建ul元素(父节点) var ul = document.createElement("ul"); ul.className = "my-list"; // 3. 循环创建4个li子元素 for (var i = 1; i <= 4; i++) { // 3.1 创建li元素 var li = document.createElement("li"); // 3.2 添加文本内容(两种方式任选) li.innerText = "列表项" + i; // 方式1:直接用innerText(简洁) // var textNode = document.createTextNode("列表项" + i); // 方式2:创建文本节点 // li.appendChild(textNode); // 3.3 添加到ul中 ul.appendChild(li); } // 4. 将ul添加到容器(此时才显示在页面上) container.appendChild(ul); </script>

实战 2:删除、替换、插入节点

需求:基于上面的列表,删除第一个 li、替换最后一个 li、在第一个 li 前插入新 li。

html
复制代码
<ul id="my-list"> <li>列表项1</li> <li>列表项2</li> <li>列表项3</li> <li>列表项4</li> </ul> <script> var ul = document.getElementById("my-list"); // 1. 删除第一个li var firstLi = ul.firstElementChild; // 找到第一个li ul.removeChild(firstLi); // 父元素删除子元素 // 2. 替换最后一个li var lastLi = ul.lastElementChild; // 找到最后一个li var newLi = document.createElement("li"); newLi.innerText = "替换后的列表项4"; ul.replaceChild(newLi, lastLi); // 替换 // 3. 插入新li到第一个位置 var currentFirstLi = ul.firstElementChild; // 当前第一个li var insertLi = document.createElement("li"); insertLi.innerText = "插入的新列表项0"; ul.insertBefore(insertLi, currentFirstLi); // 插入 </script>

3.5 查找 HTML 父子:在家族树里找 “亲戚”

【梗版解析】

兄弟,你家亲戚多了,总得知道谁是你爸(父元素)、谁是你哥(兄弟元素)吧?DOM 给你 8 个 “认亲工具”,秒辨亲戚关系:

  1. parentNode(找亲爹):比如你是抽屉(li),喊li.parentNode,立马找到柜子(ul)—— 亲爹没跑;
  2. childNodes(找所有孩子):柜子(ul)喊ul.childNodes,会把抽屉(li)和抽屉之间的缝隙(空白文本节点)全算上 —— 相当于把你家孩子和家里的垃圾全指给你看,有点坑;
  3. children(找亲生孩子):柜子(ul)喊ul.children,只认抽屉(li),忽略缝隙(空白节点)—— 精准找亲生孩子,推荐用;
  4. firstElementChild(找大儿子):柜子(ul)喊ul.firstElementChild,找到第一个抽屉(li)—— 大儿子实锤;
  5. lastElementChild(找小儿子):柜子(ul)喊ul.lastElementChild,找到最后一个抽屉(li)—— 小儿子没跑;
  6. previousElementSibling(找大哥):第二个抽屉(li)喊li.previousElementSibling,找到第一个抽屉 —— 亲大哥;
  7. nextElementSibling(找小弟):第二个抽屉(li)喊li.nextElementSibling,找到第三个抽屉 —— 亲f小弟;

记住:别用firstChildnextSibling这些坑货!它们会把缝隙(空白文本节点)当成亲戚,比如你找大儿子,结果指给你一堆垃圾,纯纯误导新手!

【专业版解析】

查找节点的 “父子兄弟关系” 是 DOM 操作的高频需求,核心依赖元素节点的属性(而非方法),需区分 “包含空白节点” 和 “仅含元素节点” 的属性:

属性名功能描述核心特点(是否含空白节点)语法示例(ul 为父元素,li 为子元素)
parentNode获取当前节点的直接父节点(唯一)含所有节点类型(元素、文本等)li.parentNode → 返回 ul 元素
childNodes获取当前节点的所有子节点(包括元素、文本、注释节点)含空白文本节点(标签间换行 / 空格),不推荐新手用ul.childNodes → 返回包含 li 和空白节点的类数组
children获取当前节点的所有子元素节点(仅含标签节点)不含空白节点,新手首选ul.children → 返回仅包含 li 元素的类数组
firstChild获取当前节点的第一个子节点(任意类型)含空白节点,易出错ul.firstChild → 可能返回空白文本节点(而非第一个 li)
firstElementChild获取当前节点的第一个子元素节点(仅含标签)不含空白节点,推荐用ul.firstElementChild → 返回第一个 li 元素
lastChild获取当前节点的最后一个子节点(任意类型)含空白节点,易出错ul.lastChild → 可能返回空白文本节点(而非最后一个 li)
lastElementChild获取当前节点的最后一个子元素节点(仅含标签)不含空白节点,推荐用ul.lastElementChild → 返回最后一个 li 元素
previousSibling获取当前节点的前一个兄弟节点(任意类型)含空白节点,易出错li.previousSibling → 可能返回空白文本节点(而非前一个 li)
previousElementSibling获取当前节点的前一个兄弟元素节点(仅含标签)不含空白节点,推荐用li.previousElementSibling → 返回前一个 li 元素
nextSibling获取当前节点的后一个兄弟节点(任意类型)含空白节点,易出错li.nextSibling → 可能返回空白文本节点(而非后一个 li)
nextElementSibling获取当前节点的后一个兄弟元素节点(仅含标签)不含空白节点,推荐用li.nextElementSibling → 返回后一个 li 元素

实战代码(新手避坑版)

html
复制代码
<ul id="my-list"> <li>列表项1</li> <li id="second-li">列表项2</li> <li>列表项3</li> </ul> <script> var ul = document.getElementById("my-list"); var secondLi = document.getElementById("second-li"); // 1. 找父节点(ul) console.log(secondLi.parentNode); // 输出ul元素 // 2. 找所有子元素(仅li,不含空白) console.log(ul.children.length); // 输出3(3个li) // 3. 找第一个子元素(列表项1) console.log(ul.firstElementChild.innerText); // 输出"列表项1" // 4. 找前一个兄弟元素(列表项1) console.log(secondLi.previousElementSibling.innerText); // 输出"列表项1" // 5. 找后一个兄弟元素(列表项3) console.log(secondLi.nextElementSibling.innerText); // 输出"列表项3" // 避坑提醒:别用firstChild! console.log(ul.firstChild); // 可能输出空白文本节点(而非li) </script>

总结:新手入门 DOM 的 “避坑口诀”

【梗版口诀】

  1. 找元素:id 最快,querySelector 最牛,className 别忘 IE 坑;
  2. 读内容:innerText 纯文字,innerHTML 带标签,style 只认行内样;
  3. 改东西:赋值就完事儿,样式要

四、DOM 文档事件:网页的 “互动开关”

【梗版解析】

兄弟,之前咱们学的 DOM 操作都是 “静态整容”—— 改个样式、加个标签,顶多算给网页 “换衣服”。而事件,才是给网页装 “互动开关”!就像你家的灯:按一下(触发事件)就亮(执行代码),再按一下就灭 —— 网页能响应用户操作,全靠事件这玩意儿!

你可以把事件想象成 “网页的情绪开关”:用户点击按钮(摸一下开关)、输入文字(拧一下旋钮)、 resize 窗口(挪一下灯位),网页都能 “感知到”,并做出反应(亮灯、变色、弹窗)。没有事件的网页,就是个不会说话的木头人;有了事件,网页才能变成会聊天、会互动的 “智能机器人”!

【专业版解析】

DOM 事件是指 “用户操作网页” 或 “网页状态变化” 时触发的行为,是 JavaScript 与网页交互的核心机制。

  • 本质:当特定行为发生时(如点击、输入、页面加载完成),浏览器会自动执行预设的 JavaScript 函数(事件处理函数);
  • 核心流程:事件触发(如点击按钮)→ 浏览器捕获事件 → 执行事件处理函数 → 事件传播(冒泡 / 捕获);
  • 意义:让静态的 HTML 页面具备 “响应能力”,是实现动态交互(如表单验证、拖拽功能、弹窗提示)的基础。

4.4.1 事件概述:互动的 “触发规则”

【梗版解析】

事件就是 “网页的触发规则”:你定好 “如果用户做 X,就执行 Y”—— 比如 “如果用户点按钮(X),就弹窗说‘你好’(Y)”,这就是一个完整的事件绑定。

简单说:事件 = 触发条件(用户操作 / 状态变化) + 响应动作(JS 代码)。

就像你给女朋友定的规矩:“如果她下班(触发条件),就给她带奶茶(响应动作)”—— 逻辑完全一样!

【专业版解析】

HTML 事件是浏览器定义的 “行为触发机制”,当满足特定条件时,会执行关联的 JavaScript 脚本。

  • 触发源:分为 “用户操作”(如点击、输入、键盘按键)和 “网页状态变化”(如页面加载完成、表单验证失败);
  • 核心要素:事件源(触发事件的元素,如按钮)、事件类型(如点击click、输入input)、事件处理函数(触发后执行的 JS 函数);
  • 绑定方式:通过 HTML 属性(如onclick)、DOM 属性(如btn.onclick = 函数)、addEventListener方法(推荐)绑定事件处理函数。

4.4.2 窗口事件:网页的 “全局开关”

【梗版解析】

窗口事件就是 “整个网页窗口的开关”—— 不是针对某个按钮、某个输入框,而是针对整个浏览器窗口,相当于你家的 “总电闸”:

  1. onblur(窗口失焦):你切到别的软件(比如从浏览器切到微信),网页就 “委屈巴巴” 地说 “你不理我了”(执行代码);
  2. onfocus(窗口获焦):你切回浏览器,网页就 “兴高采烈” 地说 “你终于回来了”;
  3. onload(页面加载完成):网页加载完所有图片、标签,相当于 “装修完开门迎客”,此时再执行代码才不会出错(比如找元素不会找不到);
  4. onresize(窗口缩放):你拉大 / 缩小浏览器窗口,网页就 “喊疼”:“别挤我!我在变形!”;
  5. onstorage(存储变化):网页的 “小仓库”(localStorage)数据变了,网页就 “警觉”:“谁动了我的东西!”。

【专业版解析】

窗口事件是由window对象(或<body>标签)触发的事件,作用于整个网页,常用事件及说明如下:

事件属性触发条件核心用途
onblur窗口失去焦点(如切换到其他应用)隐藏敏感内容、暂停动画
onfocus窗口获得焦点(如切回浏览器)显示内容、继续动画
onload页面所有资源(HTML、CSS、图片)加载完成初始化页面(如绑定事件、渲染数据),避免 “元素未加载就操作” 的错误
onresize窗口大小改变(如拉伸浏览器)响应式布局调整(如重新计算元素位置、大小)
onstorageWeb Storage(localStorage/sessionStorage)数据更新同步多标签页数据(如登录状态同步)
  • 实战代码(新手友好版)
html
复制代码
<!-- 窗口失焦/获焦示例 --> <script> // 窗口失去焦点:输出“你不理我了😭” window.onblur = function() { console.log("你不理我了😭"); }; // 窗口获得焦点:输出“欢迎回来!🥳” window.onfocus = function() { console.log("欢迎回来!🥳"); }; // 页面加载完成:输出“我准备好了!” window.onload = function() { console.log("页面加载完成,我准备好了!"); }; // 窗口缩放:输出“别挤我!” window.onresize = function() { console.log("别挤我!窗口大小变了!"); }; </script>

4.4.3 表单事件:输入框的 “互动规则”

【梗版解析】

表单事件就是 “输入框、按钮这些表单元素的专属开关”—— 相当于你家的 “智能门锁”:输入密码(触发事件)、验证通过(执行代码)、开门(提交表单)。

常用的表单事件全是 “输入框的小情绪”:

  1. onfocus/onblur:输入框 “被选中”(获焦)时变红,“被放弃”(失焦)时变绿 —— 像极了考试时被老师盯着(获焦)紧张,老师走了(失焦)放松;
  2. onchange:输入框内容改了,且 “鼠标移走” 才触发 —— 比如你在输入框写 “123”,没移走时不算,移到别的地方才会执行代码,像极了 “写完作业才交差”;
  3. oninput:输入框内容 “一改变就触发”—— 你敲一个字就执行一次代码,像极了 “写一个字就给老师看一眼”,实时反馈;
  4. oninvalid:表单验证失败(如必填项没填)—— 像极了 “作业没写完就交,老师打回重写”;
  5. onselect:选中文本框内容 —— 像极了 “老师圈出你写的重点”,网页会知道 “你选中了啥”;
  6. onsubmit:提交表单 —— 像极了 “交作业”,提交前可以检查作业(验证表单),不合格就不让交(return false阻止提交)。

【专业版解析】

表单事件是针对<form>及表单元素(<input><select><textarea>)的事件,核心用于表单交互和验证,常用事件及说明如下:

事件属性触发条件核心用途
onfocus表单元素获得焦点(如点击输入框)高亮元素、提示输入规则
onblur表单元素失去焦点(如点击输入框外)验证输入内容(如检查手机号格式)
onchange表单元素内容改变且失焦(适用于输入框、下拉框)联动更新(如下拉框选 “省份” 后,更新 “城市” 选项)
oninput表单元素内容实时改变(输入框每输入一个字符)实时验证(如输入密码时提示 “密码强度”)
oninvalid表单提交时元素验证失败(如required必填项未填)自定义错误提示
onselect选中文本框 / 文本域的内容高亮选中内容、复制时触发操作
onsubmit点击表单的提交按钮(<input type="submit">表单整体验证(如检查所有必填项),return false可阻止默认提交行为

实战代码(新手友好版)

html
复制代码
<form id="myForm"> <input type="text" id="userInput" required placeholder="请输入内容"> <input type="submit" value="提交"> </form> <script> var input = document.getElementById("userInput"); var form = document.getElementById("myForm"); // 1. 获焦时背景变红 input.onfocus = function() { this.style.background = "red"; // this指向触发事件的input元素 }; // 2. 失焦时背景变绿,且验证内容 input.onblur = function() { this.style.background = "green"; if (this.value.length < 3) { alert("内容不能少于3个字符!"); } }; // 3. 实时输入时,控制台输出内容 input.oninput = function() { console.log("实时输入:", this.value); }; // 4. 选中内容时提示 input.onselect = function() { alert("你选中了内容!"); }; // 5. 表单提交时验证 form.onsubmit = function() { if (input.value === "123") { alert("提交成功!"); return true; // 允许提交 } else { alert("内容不对,提交失败!"); return false; // 阻止提交 } }; </script>

4.4.4 键盘事件:键盘的 “按键指令”

【梗版解析】

键盘事件就是 “键盘的按键指令”—— 相当于你给网页装了 “游戏手柄”:按 WASD 移动角色,按空格跳跃,按 A 键触发技能!

常用的三个键盘事件,像极了 “按键的三步走”:

  1. onkeydown:按键 “按下的瞬间” 触发 —— 比如按 A 键,一按下去就执行,哪怕按住不放会连续触发(像打游戏时按住前进键一直走);
  2. onkeyup:按键 “松开的瞬间” 触发 —— 比如按 A 键后松手,才执行一次(像松开前进键停止);
  3. onkeypress:按键 “按下并松开” 触发 —— 相当于keydown + keyup,但不识别功能键(如 Shift、Ctrl)。

最牛的是 “按键编码”(keyCode):每个按键都有专属编号 ——A 是 65,D 是 68,上箭头是 38,下箭头是 40。就像每个士兵都有编号,你可以指定 “只响应编号 65 的按键(A 键)”,实现 “按 A 键才触发” 的效果。

比如用方向键控制 div 移动,就像玩贪吃蛇:按左键 div 往左挪,按右键往右挪 —— 本质就是监听keydown事件,判断按键编号,然后修改 div 的left/top值!

【专业版解析】

键盘事件是由键盘操作触发的事件,适用于需要 “按键控制” 的场景(如游戏、快捷键、输入限制),常用事件及说明如下:

事件属性触发条件核心特点
onkeydown按键按下瞬间(按住会连续触发)识别所有按键(包括功能键 Shift、Ctrl、方向键)
onkeyup按键松开瞬间仅触发一次,适合 “单次按键” 响应(如确认操作)
onkeypress按键按下并松开不识别功能键,仅识别字符键(如字母、数字、符号)

关键补充(事件对象)

事件触发时,浏览器会自动传递一个 “事件对象”(event),包含按键信息:

  • event.keyCode:按键的编码(如 A=65、左箭头 = 37、右箭头 = 39);
  • event.ctrlKey/altKey/shiftKey:是否同时按下 Ctrl/Alt/Shift 键(布尔值);
  • 兼容性:IE8 及以下不传递事件对象,需用window.event获取,兼容写法:event = event || window.event

实战代码(方向键控制 div 移动)

html
复制代码
<div id="box" style="width: 100px; height: 100px; background: red; position: absolute;"></div> <script> var box = document.getElementById("box"); // 监听键盘按下事件 document.onkeydown = function(event) { // 兼容IE8及以下 event = event || window.event; var speed = 10; // 移动速度(像素/次) // 判断按键编号,控制移动方向 switch(event.keyCode) { case 37: // 左箭头 box.style.left = box.offsetLeft - speed + "px"; break; case 39: // 右箭头 box.style.left = box.offsetLeft + speed + "px"; break; case 38: // 上箭头 box.style.top = box.offsetTop - speed + "px"; break; case 40: // 下箭头 box.style.top = box.offsetTop + speed + "px"; break; } }; </script>

4.4.5 鼠标事件:鼠标的 “操作指令”(补全)

【梗版解析】

  1. 按住鼠标(onmousedown):相当于你抓住桌子腿 —— 记录鼠标和 div 的 “距离差”(比如鼠标在 div 左边 50px 处按下,移动时 div 就得跟着这个差距走,不然会 “飞出去”);
  2. 移动鼠标(onmousemove):相当于你拖着桌子走 —— 根据鼠标的实时位置,减去之前记录的 “距离差”,就是 div 该在的位置,实时更新lefttop值;
  3. 松开鼠标(onmouseup):相当于你松开桌子腿 —— 停止移动,不然鼠标移到天涯海角,div 也会跟着跑(得把mousemove事件清空)。

还有个坑:拖拽时浏览器会默认 “搜索选中的内容”,导致 div 拖不动,这时候加个return false就能搞定,相当于告诉浏览器 “别瞎忙活,我在拖东西呢!”

【专业版解析】

实战代码(通用拖拽功能,兼容所有浏览器)

html
复制代码
<div id="box1" style="width: 100px; height: 100px; background: red; position: absolute;"></div> <div id="box2" style="width: 100px; height: 100px; background: green; position: absolute; left: 200px;"></div> <script> // 调用拖拽函数,给两个div都加上拖拽功能 drag(document.getElementById("box1")); drag(document.getElementById("box2")); // 通用拖拽函数:参数obj为要拖拽的元素 function drag(obj) { // 1. 鼠标按下时,记录偏移量 obj.onmousedown = function(event) { // 兼容IE8及以下的事件对象 event = event || window.event; // 记录鼠标与元素的偏移量(避免鼠标在元素内不同位置按下时,元素“跳走”) var offsetX = event.clientX - obj.offsetLeft; var offsetY = event.clientY - obj.offsetTop; // 2. 鼠标移动时,元素跟随移动(绑定给document,避免鼠标移出元素后停止移动) document.onmousemove = function(event) { event = event || window.event; // 计算元素新位置 var newLeft = event.clientX - offsetX; var newTop = event.clientY - offsetY; // 设置元素位置 obj.style.left = newLeft + "px"; obj.style.top = newTop + "px"; }; // 3. 鼠标松开时,停止拖拽 document.onmouseup = function() { // 清空mousemove和mouseup事件,避免元素继续跟随 document.onmousemove = null; document.onmouseup = null; }; // 阻止浏览器默认行为(避免拖拽时选中文字、搜索内容) return false; }; } </script>

4.4.6 媒体事件:音视频的 “状态开关”

【梗版解析】

媒体事件就是给<video><audio>这些音视频元素,配了个 “戏精经纪人”!它们全程监控音视频的 “表演状态”,一有风吹草动就给网页发消息,比狗仔队还敬业!

  1. oncanplay(能播放了):音视频加载了一点数据,够播个开头了,经纪人立马喊:“导演!演员准备好露脸了!快给镜头!”(网页显示 “播放按钮”);
  2. oncanplaythrough(全程能播):音视频加载完了,不用缓冲,经纪人狂喜:“完美!演员状态拉满,全程无尿点,直接开演!”(网页显示 “全屏播放”);
  3. onplay(开始播放):用户点了播放,经纪人喊:“Action!灯光音响准备,演员开始表演!”(网页隐藏 “播放”,显示 “暂停”);
  4. onpause(暂停播放):用户点了暂停,经纪人喊:“Cut!演员歇会儿,补个妆!”(网页隐藏 “暂停”,显示 “播放”);
  5. onended(播放结束):音视频播完了,经纪人立马凑上来:“观众老爷别走!演员鞠躬谢幕,要不要再来一遍?”(网页弹窗 “是否重播”);
  6. onerror(加载失败):音视频加载出错(比如网络断了),经纪人当场崩溃:“完了完了!演员掉链子了!网络不给力,快让观众重试!”(网页显示 “加载失败,点击重试”)。

这经纪人(媒体事件)比演员还戏多,全程唠唠叨叨,但没它还真不行 —— 没有它,你根本不知道音视频是 “准备好” 了还是 “凉透了”!

【专业版解析】

媒体事件是针对<video><audio><img>等媒体元素的事件,用于监控媒体的 “加载、播放、状态变化”,常用事件及说明如下:

事件属性触发条件核心用途
oncanplay媒体已加载部分数据,可开始播放(可能需缓冲)显示 “播放按钮”、提示用户 “可播放”
oncanplaythrough媒体已加载完成,无需缓冲即可播至结尾显示 “全程流畅播放” 提示
onplay媒体开始播放时隐藏 “播放按钮”、显示 “暂停按钮”
onpause媒体暂停时显示 “播放按钮”、隐藏 “暂停按钮”
onended媒体播放结束时自动重播、显示 “播放完毕” 提示、跳转页面
onerror媒体加载 / 播放出错时(如网络错误、文件损坏)显示错误提示、提供重试按钮
ontimeupdate媒体播放位置变化时(如进度条移动)更新进度条、显示当前播放时间

实战代码(戏精视频示例)

html
复制代码
<video id="myVideo" controls src="test.mp4" width="300" poster="cover.jpg"></video> <button id="retryBtn" style="display: none;">加载失败,点击重试</button> <script> var video = document.getElementById("myVideo"); var retryBtn = document.getElementById("retryBtn"); // 1. 能播放了:提示“可以开演啦” video.oncanplay = function() { console.log("🎬 演员准备就绪,点击播放开演!"); }; // 2. 开始播放:喊“Action” video.onplay = function() { console.log("🎬 Action!灯光音响准备,演员表演中~"); }; // 3. 暂停播放:喊“Cut” video.onpause = function() { console.log("🎬 Cut!演员歇会儿,喝口水~"); }; // 4. 播放结束:求重播 video.onended = function() { alert("🎬 表演结束!演员鞠躬~ 要不要再来一遍?"); this.play(); // 自动重播 }; // 5. 加载失败:崩溃求重试 video.onerror = function() { console.log("😱 完了完了!演员掉链子了,网络不给力!"); retryBtn.style.display = "block"; // 显示重试按钮 }; // 重试按钮点击事件 retryBtn.onclick = function() { video.src = "test.mp4"; // 重新设置视频地址 video.load(); // 重新加载 this.style.display = "none"; console.log("🔄 正在重试加载,演员加油啊!"); }; </script>

4.4.8 事件冒泡:事件的 “连锁碰瓷”

【梗版解析】

事件冒泡就是 DOM 事件里的 “连锁碰瓷”—— 相当于你在公司走廊不小心踩了实习生(点击子元素),实习生立马躺地上喊 “疼”,然后组长跑过来喊 “我的人你也敢踩”,总监跑过来喊 “敢在我地盘搞事”,最后 CEO 都知道了 —— 事件从子元素 “碰瓷” 到父元素,再到爷爷元素,一路往上闹,这就是冒泡!

比如你有两个嵌套的 div:大 div(爸爸)里套小 div(儿子),都绑定了点击事件。你点击儿子 div,儿子喊 “我被点了!”,爸爸立马跟着喊 “我儿子被点了!我也得喊!”—— 这就是事件冒泡,儿子碰瓷,爸爸跟着凑热闹!

大多数时候冒泡是 “帮凶”(比如事件委派),但有时候也坑 —— 比如你只想让儿子喊,不想让爸爸喊,就得 “阻止碰瓷”,相当于给爸爸塞了个红包:“别闹了,这事跟你没关系!”,爸爸就不跟着喊了!

【专业版解析】

事件冒泡(Bubble)是 DOM 事件的默认传播机制:当后代元素的事件被触发时,其祖先元素的相同事件会从 “子元素→父元素→爷爷元素→...→window” 依次向上触发。

  • 触发逻辑:事件源(子元素)→ 直接父元素 → 所有祖先元素 → window;
  • 阻止方法:通过事件对象的stopPropagation()(主流浏览器)或window.event.cancelBubble = true(IE8 及以下),中断事件向上传播;
  • 核心价值:是 “事件委派” 的基础(后续重点讲),但嵌套元素的相同事件需避免重复触发时,需阻止冒泡。

实战代码(阻止事件碰瓷示例)

html
复制代码
<div id="dad" style="width:200px; height:200px; background:#ffc1e9; padding:20px;"> 爸爸div(点我喊“我是爸爸!”) <div id="son" style="width:100px; height:100px; background:#98d8c8;"> 儿子div(点我喊“我是儿子!”) </div> </div> <script> var dad = document.getElementById("dad"); var son = document.getElementById("son"); // 爸爸的点击事件(碰瓷党) dad.onclick = function() { console.log("👨 我是爸爸!我儿子被点了,我也得凑个热闹!"); }; // 儿子的点击事件(阻止爸爸碰瓷) son.onclick = function(event) { console.log("👦 我是儿子!我被点了,但我不让爸爸碰瓷!"); // 阻止事件冒泡(阻止爸爸的事件触发) stopBubble(event); }; // 兼容所有浏览器的“阻止碰瓷”函数 function stopBubble(event) { if (event && event.stopPropagation) { event.stopPropagation(); // 主流浏览器:让碰瓷链中断 } else { window.event.cancelBubble = true; // IE8及以下:给爸爸塞红包,别闹了 } } </script>
  • 效果:点击儿子 div,只输出 “👦 我是儿子!...”,爸爸的事件不触发;注释掉stopBubble(event),点击儿子会同时输出父子俩的日志(连锁碰瓷成功)。

4.4.9 事件委派:全家的 “统一碰瓷规则”

【梗版解析】

事件委派就是 “给全家定一个统一的碰瓷规则,不用单独给每个人定”—— 比如你家有 10 个孩子(10 个<a>标签),想让每个孩子被点时都喊 “我被点了!”,不用给每个孩子单独教 “碰瓷话术”,直接跟爸爸(<ul>标签)说:“所有孩子被点时,都按这个话术喊!”,这就是事件委派!

核心逻辑:利用事件冒泡(碰瓷链),把事件绑定给 “所有孩子的共同爸爸”,孩子被点(触发事件)后,会冒泡到爸爸那里,爸爸再判断 “是谁被点了”,如果是自己的孩子,就让它按规则喊 —— 相当于爸爸当 “碰瓷总指挥”,统一管理所有孩子的碰瓷行为!

好处简直绝了:一是省事儿(绑定一次就行,不用绑 10 次);二是支持 “新增孩子”(后来生的第 11 个孩子,不用重新教话术,被点时也会喊)—— 老油条做项目时,只要是批量元素(比如列表、菜单),必用事件委派,省时间还少出错,简直是 “碰瓷界的管理大师”!

【专业版解析】

事件委派是基于 “事件冒泡” 的优化方案,核心是 “将事件绑定给祖先元素,代理后代元素的事件”。

  • 核心原理:后代元素触发事件后,事件通过冒泡传递到祖先元素,祖先元素通过事件对象的target(主流浏览器)/srcElement(IE8)获取 “真正触发事件的后代元素”,再判断是否是目标元素,执行对应逻辑;
  • 核心优势:
    1. 减少事件绑定次数(从 “N 个后代元素绑定 N 次” 变为 “1 个祖先元素绑定 1 次”),提升页面性能;
    2. 支持动态新增后代元素(新增元素无需重新绑定事件,自动继承规则);
  • 适用场景:批量相同元素的事件绑定(如列表项、菜单链接、表格行)。

实战代码(爸爸当碰瓷总指挥示例)

html
复制代码
<ul id="family" style="list-style: none; padding: 0;"> <li><a href="javascript:;" class="kid">孩子1</a></li> <li><a href="javascript:;" class="kid">孩子2</a></li> <li><a href="javascript:;" class="kid">孩子3</a></li> </ul> <script> var family = document.getElementById("family"); // 事件委派:爸爸当总指挥,绑定一次事件 family.onclick = function(event) { event = event || window.event; // 获取真正被点的孩子(触发事件的原始元素) var target = event.target || event.srcElement; // 判断是不是自己的孩子(class为kid的a标签) if (target.className === "kid") { console.log(`👶 ${target.innerText}被点了!喊:“我被碰瓷啦!爸爸救我!”`); } }; // 动态新增孩子(不用重新绑定事件,自动继承碰瓷规则) var newKid = document.createElement("li"); newKid.innerHTML = '<a href="javascript:;" class="kid">孩子4(新出生的)</a>'; family.appendChild(newKid); </script>
  • 效果:点击原有 3 个孩子和新增的 “孩子 4”,都会输出对应的 “碰瓷话术”,无需给新增孩子单独绑定事件。

4.4.10 事件绑定:给开关装 “多个灯泡”(补全)

【梗版解析】

(接上文)老油条的 “装灯 / 拆灯” 函数,就像给开关装了 “智能灯泡控制器”—— 不管是主流浏览器还是 IE 这个 “老顽固”,都能完美适配!

举个栗子:你给按钮 1 装了两个灯泡(函数 f1 和 f2),按一下按钮 1,两个灯泡同时亮(输出 “函数 1 执行了” 和 “函数 2 执行了”);再按按钮 2,拆掉灯泡 f1,之后按按钮 1,就只剩 f2 亮了 —— 这操作比家里换灯泡还简单!

重点提醒:装灯时不能用 “匿名灯泡”(匿名函数),比如addEvent(btn, "click", function(){}),拆灯时会找不到这个灯泡!必须给灯泡起名字(命名函数),比如 f1、f2,拆灯时才能精准定位 —— 就像你给灯泡贴了标签,拆的时候不会拆错!

【专业版解析】

(接上文实战代码)完整事件绑定 / 解绑示例 + 解析

html
复制代码
<button id="btn1">按钮1(装了两个灯泡)</button> <button id="btn2">按钮2(拆掉灯泡f1)</button> <script> // 定义两个“带名字的灯泡”(命名函数) function f1() { console.log("💡 灯泡1亮了!函数1执行了~"); } function f2() { console.log("💡 灯泡2亮了!函数2执行了~"); } // 兼容所有浏览器的“装灯”函数 function addEvent(element, type, fn) { if (element.addEventListener) { // 主流浏览器:正常装灯 element.addEventListener(type, fn, false); } else if (element.attachEvent) { // IE8及以下:老顽固专属装灯方式,要加“on”前缀 element.attachEvent("on" + type, fn); } else { // 远古浏览器:直接把灯泡拧在开关上(兼容底线) element["on" + type] = fn; } } // 兼容所有浏览器的“拆灯”函数 function removeEvent(element, type, fn) { if (element.removeEventListener) { // 主流浏览器:正常拆灯 element.removeEventListener(type, fn, false); } else if (element.detachEvent) { // IE8及以下:老顽固专属拆灯方式 element.detachEvent("on" + type, fn); } else { // 远古浏览器:直接把灯泡拧下来 element["on" + type] = null; } } // 给按钮1装两个灯泡(绑定f1和f2) addEvent(document.getElementById("btn1"), "click", f1); addEvent(document.getElementById("btn1"), "click", f2); // 给按钮2绑定“拆灯”事件(拆掉按钮1的灯泡f1) document.getElementById("btn2").onclick = function() { removeEvent(document.getElementById("btn1"), "click", f1); alert("🔧 已拆掉灯泡f1!再按按钮1只剩灯泡f2亮~"); }; </script>
  • 核心注意点:
    1. 绑定命名函数:解绑时必须传入与绑定相同的函数引用(如 f1、f2),匿名函数无法解绑;
    2. IE 兼容:attachEvent需加 “on” 前缀(如onclick),且函数内this指向window(非事件源),如需获取事件源,需用event.srcElement
    3. 传播阶段:addEventListener第三个参数useCapture默认为false(冒泡阶段触发),一般无需修改。

4.4.11 事件传播:事件的 “碰瓷三部曲”

【梗版解析】

事件传播这事儿,当年网景和微软吵得像 “菜市场大妈掐架”!网景说:“碰瓷得从老板开始!先碰 CEO,再碰总监,最后碰实习生!”(由外向内,捕获阶段);微软说:“放屁!碰瓷得从实习生开始!先碰小的,再往上闹!”(由内向外,冒泡阶段)。

最后 W3C 拍板:“别吵了!都听我的!碰瓷分三步,先搜捕,再碰瓷,最后上报!” 这就是事件传播的 “三部曲”:

  1. 捕获阶段:事件从最外层祖先(window)往目标元素 “搜捕”—— 相当于警察接到碰瓷报警,从公司门口往办公室找,找到实习生(目标元素)之前,不行动,只围观;
  2. 目标阶段:事件找到目标元素(实习生),实习生开始碰瓷(触发事件)—— 警察找到实习生,实习生躺地上喊疼;
  3. 冒泡阶段:事件从目标元素往回传(实习生→组长→总监→CEO),依次触发祖先元素的事件 —— 实习生喊疼,组长跟着闹,总监跟着喊,最后 CEO 都知道了。

平时咱们用的事件,默认都在 “冒泡阶段” 碰瓷(addEventListener第三个参数为false);如果想在 “捕获阶段” 就动手(比如警察还没找到实习生,就先把组长抓起来),把第三个参数设为true就行 —— 但这操作太奇葩,基本用不上,新手不用管,知道有这出 “碰瓷大戏” 就行!

【专业版解析】

W3C 标准将事件传播分为三个阶段,依次执行,核心规则如下:

传播阶段执行顺序核心行为戏精翻译
捕获阶段最外层祖先 → 目标元素的父元素 → 目标元素事件从顶层往下 “搜索” 目标元素,默认不触发事件处理函数“警察搜捕碰瓷者,只围观不行动”
目标阶段目标元素事件找到目标,触发目标元素的事件处理函数“碰瓷者开始表演,躺地喊疼”
冒泡阶段目标元素 → 父元素 → 最外层祖先事件从目标元素往上 “传播”,依次触发祖先元素的事件处理函数“碰瓷者往上闹,组长→总监→CEO 依次跟风”
  • 关键补充:
    1. 捕获阶段触发:将addEventListener第三个参数设为true,事件会在捕获阶段触发(如element.addEventListener("click", fn, true));
    2. 阻止传播:用event.stopPropagation()(主流)或window.event.cancelBubble = true(IE8),可中断捕获和冒泡阶段(相当于警察制止碰瓷,不让事件往上闹);
    3. IE 兼容性:IE8 及以下没有 “捕获阶段”,事件仅在冒泡阶段执行。

实战代码(事件传播三部曲示例)

html
复制代码
<div id="ceo" style="width:300px; height:300px; background:#ff6b6b; padding:20px;"> CEO(最外层) <div id="director" style="width:200px; height:200px; background:#4ecdc4; padding:20px;"> 总监(中间层) <div id="intern" style="width:100px; height:100px; background:#ffeaa7;"> 实习生(目标元素) </div> </div> </div> <script> var ceo = document.getElementById("ceo"); var director = document.getElementById("director"); var intern = document.getElementById("intern"); // 绑定事件,第三个参数设为true(捕获阶段触发) ceo.addEventListener("click", function() { console.log("👔 CEO:捕获阶段 - 收到碰瓷警报,开始搜捕!"); }, true); director.addEventListener("click", function() { console.log("👔 总监:捕获阶段 - 搜捕到中间层,继续往办公室走!"); }, true); intern.addEventListener("click", function() { console.log("👔 实习生:目标阶段 - 我就是碰瓷的!躺地喊疼!"); }, true); // 绑定事件,第三个参数设为false(冒泡阶段触发,默认) ceo.addEventListener("click", function() { console.log("👔 CEO:冒泡阶段 - 碰瓷闹到我这了!快处理!"); }, false); director.addEventListener("click", function() { console.log("👔 总监:冒泡阶段 - 碰瓷闹到我这了!上报CEO!"); }, false); intern.addEventListener("click", function() { console.log("👔 实习生:冒泡阶段 - 我先喊,让组长背锅!"); }, false); </script>
  • 效果:点击 “实习生” div,控制台输出顺序(捕获→目标→冒泡):
    1. CEO:捕获阶段 - 收到碰瓷警报,开始搜捕!
    2. 总监:捕获阶段 - 搜捕到中间层,继续往办公室走!
    3. 实习生:目标阶段 - 我就是碰瓷的!躺地喊疼!
    4. 实习生:冒泡阶段 - 我先喊,让组长背锅!
    5. 总监:冒泡阶段 - 碰瓷闹到我这了!上报 CEO!
    6. CEO:冒泡阶段 - 碰瓷闹到我这了!快处理!

4.4.12 事件默认行为:浏览器的 “祖传习惯”

【梗版解析】

浏览器这玩意儿,有很多 “祖传习惯”(默认行为)—— 比如点击<a>标签会跳转、表单提交会刷新页面、右键会弹出默认菜单,这些都是浏览器 “天生就会” 的操作,相当于 “祖传的碰瓷流程”!

但有时候这些习惯很坑:比如你给<a>标签绑定了 “点击弹窗” 事件,结果点击后既弹窗,又跳转页面 —— 弹窗刚出来就被刷新冲掉了,戏都没演完!这时候就得 “阻止浏览器的祖传习惯”,让它听你的话!

阻止默认行为就像 “给浏览器说:别按老规矩来,听我的!”—— 比如给<a>标签加return false,或者用event.preventDefault(),浏览器就不会跳转了,只会执行你写的弹窗事件!

【专业版解析】

事件默认行为是浏览器对特定元素事件的 “默认响应”(如<a>跳转、表单提交刷新页面),需根据需求决定是否阻止。

  • 常见默认行为:
    1. <a href="xxx">:点击后跳转至href指定地址;
    2. <form>:提交时刷新页面;
    3. 右键点击:弹出浏览器默认上下文菜单;
    4. 输入框:按 Enter 键提交表单。
  • 阻止方法:
    1. 主流浏览器:event.preventDefault()
    2. IE8 及以下:window.event.returnValue = false
    3. 简化写法:事件处理函数末尾return false(兼容多数场景,但 IE8 部分事件不支持)。

实战代码(阻止浏览器祖传习惯示例)

html
复制代码
<!-- 阻止a标签跳转 --> <a href="https://www.baidu.com" id="myLink">点我弹窗,不跳转百度!</a> <!-- 阻止表单提交刷新页面 --> <form id="myForm"> <input type="text" placeholder="输入内容"> <input type="submit" value="提交不刷新"> </form> <script> // 1. 阻止a标签跳转 var link = document.getElementById("myLink"); link.onclick = function(event) { alert("🔗 我弹窗了,但不跳转百度!"); // 阻止默认行为(跳转) stopDefault(event); }; // 2. 阻止表单提交刷新 var form = document.getElementById("myForm"); form.onsubmit = function(event) { alert("📝 表单提交了,但不刷新页面!"); stopDefault(event); return false; // 双重保险,确保阻止 }; // 兼容所有浏览器的“阻止祖传习惯”函数 function stopDefault(event) { if (event && event.preventDefault) { event.preventDefault(); // 主流浏览器:让浏览器别按老规矩来 } else { window.event.returnValue = false; // IE8及以下:给浏览器说“不” } } </script>

总结:JS DOM 事件的 “戏精修炼手册”

【梗版总结】

  1. 事件是网页的 “戏精开关”—— 用户一操作,网页就演戏,互动感全靠它;
  2. 常用事件分 5 类:窗口事件(全局戏精)、表单事件(输入框戏精)、键盘事件(游戏手柄)、鼠标事件(粘人小猫)、媒体事件(经纪人附体);
  3. 三大坑要避开:事件冒泡(连锁碰瓷)、默认行为(祖传习惯)、IE 兼容(老顽固搞特殊);
  4. 两大神器要记牢:事件委派(全家统一碰瓷规则)、addEventListener(开关装多个灯泡)。

其实 DOM 事件一点都不难,就像给网页写 “戏精剧本”—— 你想让网页怎么演,就绑定对应的事件,阻止不想让它演的部分,新手多抄几遍代码,多改改 “剧本”,很快就能上手!

【专业总结】

  1. 核心逻辑:事件三要素(事件源、事件类型、处理函数)+ 传播三阶段(捕获、目标、冒泡);
  2. 关键方法:addEventListener(多函数绑定)、stopPropagation(阻止传播)、preventDefault(阻止默认行为);
  3. 优化方案:事件委派(减少绑定次数、支持动态元素);
  4. 兼容要点:处理 IE8 的attachEventcancelBubblereturnValue,封装通用绑定 / 解绑 / 阻止函数。

DOM 事件是前端交互的核心,掌握它就能实现 90% 的网页动态功能(如表单验证、拖拽、弹窗、游戏),新手需重点练习 “事件绑定→阻止默认行为→事件委派” 的组合使用,多实战就能熟练掌握!

DOM 事件核心代码速查表(新手入门必备)

一、核心事件绑定 / 解绑(兼容所有浏览器)

1. 事件绑定(给元素装 “戏精开关”)

javascript
复制代码
// 通用绑定函数:element=元素,type=事件类型(如"click"),fn=处理函数 function addEvent(element, type, fn) { if (element.addEventListener) { element.addEventListener(type, fn, false); // 主流浏览器(冒泡阶段) } else if (element.attachEvent) { element.attachEvent("on" + type, fn); // IE8及以下(加"on"前缀) } else { element["on" + type] = fn; // 远古浏览器兼容 } }

2. 事件解绑(给元素拆 “戏精开关”)

javascript
复制代码
// 通用解绑函数:需传入绑定的同一命名函数(不能是匿名函数) function removeEvent(element, type, fn) { if (element.removeEventListener) { element.removeEventListener(type, fn, false); // 主流浏览器 } else if (element.detachEvent) { element.detachEvent("on" + type, fn); // IE8及以下 } else { element["on" + type] = null; // 远古浏览器兼容 } }

二、常用事件实战代码(直接抄)

1. 窗口事件(网页全局戏精)

javascript
复制代码
// 1. 页面加载完成(化妆完毕营业) window.onload = function() { console.log("页面加载完成,可开始操作!"); }; // 2. 窗口获焦/失焦(粘人提醒) window.onfocus = function() { console.log("欢迎回来~"); }; window.onblur = function() { console.log("别离开我呀!"); }; // 3. 窗口缩放(哀嚎求救) window.onresize = function() { console.log("窗口被拉伸了!"); };

2. 表单事件(输入框戏精日常)

html
复制代码
<input type="text" id="input" required placeholder="请输入内容"> <form id="form"> <input type="submit" value="提交"> </form> <script> var input = document.getElementById("input"); var form = document.getElementById("form"); // 1. 获焦/失焦(变色提醒) input.onfocus = function() { this.style.background = "#ffeeee"; // 获焦变红 }; input.onblur = function() { this.style.background = "#eeffee"; // 失焦变绿 if (this.value.length < 3) alert("内容不能少于3个字符!"); // 失焦验证 }; // 2. 实时输入(彩虹屁) input.oninput = function() { console.log("你输入了:" + this.value + ",真好看!"); }; // 3. 表单提交(阻止刷新) form.onsubmit = function(event) { alert("提交成功!"); stopDefault(event); // 阻止默认刷新行为 return false; // 双重保险 }; </script>

3. 键盘事件(游戏手柄模式)

html
复制代码
<div id="box" style="width:50px; height:50px; background:red; position:absolute;"></div> <script> var box = document.getElementById("box"); var speed = 10; // 移动速度 // 方向键控制移动(上下左右箭头) document.onkeydown = function(event) { event = event || window.event; switch(event.keyCode) { case 37: box.style.left = box.offsetLeft - speed + "px"; break; // 左 case 39: box.style.left = box.offsetLeft + speed + "px"; break; // 右 case 38: box.style.top = box.offsetTop - speed + "px"; break; // 上 case 40: box.style.top = box.offsetTop + speed + "px"; break; // 下 } }; </script>

4. 鼠标事件(粘人小猫模式)

html
复制代码
<div id="dragBox" style="width:100px; height:100px; background:blue; position:absolute;"></div> <script> // 通用拖拽函数(直接调用) function drag(obj) { obj.onmousedown = function(event) { event = event || window.event; obj.setCapture && obj.setCapture(); // IE兼容 var offsetX = event.clientX - obj.offsetLeft; var offsetY = event.clientY - obj.offsetTop; document.onmousemove = function(event) { event = event || window.event; var left = event.clientX - offsetX; var top = event.clientY - offsetY; obj.style.left = left + "px"; obj.style.top = top + "px"; }; document.onmouseup = function() { document.onmousemove = null; document.onmouseup = null; obj.releaseCapture && obj.releaseCapture(); // IE兼容 }; return false; // 阻止默认行为 }; } // 给元素绑定拖拽 drag(document.getElementById("dragBox")); </script>

5. 媒体事件(音视频经纪人)

html
复制代码
<video id="video" controls src="test.mp4" width="300"></video> <script> var video = document.getElementById("video"); // 1. 能播放了 video.oncanplay = function() { console.log("视频准备就绪,可播放!"); }; // 2. 播放/暂停 video.onplay = function() { console.log("开始播放~"); }; video.onpause = function() { console.log("暂停播放~"); }; // 3. 播放结束自动重播 video.onended = function() { alert("播放完毕,自动重播!"); this.play(); }; // 4. 加载失败 video.onerror = function() { alert("加载失败,请检查网络!"); }; </script>

6. 事件委派(全家统一戏精规则)

html
复制代码
<ul id="menu"> <li><a href="javascript:;" class="item">首页</a></li> <li><a href="javascript:;" class="item">关于我们</a></li> </ul> <script> var menu = document.getElementById("menu"); // 委派给ul,所有.item都生效 menu.onclick = function(event) { event = event || window.event; var target = event.target || event.srcElement; // 找到真正触发的元素 if (target.className === "item") { console.log("点击了:" + target.innerText); } }; // 新增元素自动生效(无需重新绑定) var newLi = document.createElement("li"); newLi.innerHTML = '<a href="javascript:;" class="item">新闻中心</a>'; menu.appendChild(newLi); </script>

三、关键工具函数(避坑必备)

1. 阻止事件冒泡(打断连锁碰瓷)

javascript
复制代码
function stopBubble(event) { if (event && event.stopPropagation) { event.stopPropagation(); // 主流浏览器 } else { window.event.cancelBubble = true; // IE8及以下 } }

2. 阻止默认行为(改掉浏览器祖传习惯)

javascript
复制代码
function stopDefault(event) { if (event && event.preventDefault) { event.preventDefault(); // 主流浏览器 } else { window.event.returnValue = false; // IE8及以下 } }

四、新手避坑指南

  1. 事件绑定用addEvent函数,避免onclick覆盖问题;
  2. 解绑必须用 “命名函数”,匿名函数无法解绑;
  3. 拖拽 / 阻止跳转时,记得加return falsestopDefault
  4. 事件委派时,用event.target找触发元素,支持动态新增;
  5. IE8 兼容重点:attachEvent加 “on” 前缀、cancelBubble阻止冒泡、returnValue阻止默认行为。
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP