前端基础——JavaScript(WebAPI)上篇:DOM 获取、事件与节点操作,附可直接粘贴运行的完整示例
文章目录一、DOM 是什么浏览器把网页解析成一棵树二、获取元素六种方式记住两种就够三、事件绑定给页面装上「耳朵」3.1 两种写法推荐一种3.2 事件对象处理函数的入参四、节点操作动态改页面4.1 改内容与改样式4.2 创建、添加、删除节点4.3 一个小需求串起三步点击按钮追加一条弹幕五、事件委托一个监听器管一万个子元素六、常见坑清单都是实际会撞上的七、完整小页面把本篇三件事全部用上学会了 JavaScript 的变量、函数、对象打开浏览器控制台想操作网页发现——嗯document是什么为什么点按钮没反应从这篇开始进入 WebAPI。它是 JS 语言本身之外浏览器提供给你的那套能力操作页面结构DOM、处理用户交互事件、发请求BOM / fetch……内容多所以拆成上下两篇。上篇只讲三件事拿到元素、绑定事件、增删节点。这是所有前端交互代码的地基表单校验、弹窗、轮播图拆开全是这三件事的组合。文末有一个把三件事全用上的完整小页面复制保存成.html双击就能跑。一、DOM 是什么浏览器把网页解析成一棵树浏览器拿到 HTML 后会把整个页面解析成一棵树DOM 树树上每个节点对应页面上的一个标签、属性或文本。JavaScript 通过document对象拿到这棵树的入口增删改查节点页面就跟着变。一句话DOM 是浏览器暴露给 JS 的「页面遥控器」。先准备一个贯穿全文的页面骨架!DOCTYPEhtmlhtmlheadmetacharsetutf-8titleWebAPI 上篇/title/headbodydividboxclasscontainerpclassmsg第一条消息/ppclassmsg第二条消息/pulidlistli苹果/lili香蕉/li/ulbuttonidbtn点我/button/divscript// 后面所有代码都写在这里/script/body/html注意 script 标签的位置它写在/body前是为了让脚本执行时页面元素已经存在。如果写在head里又不做处理document.getElementById会拿到null——这是初学者最常踩的第一个坑。二、获取元素六种方式记住两种就够方式语法返回按 iddocument.getElementById(box)单个元素或null按选择器单个document.querySelector(#box .msg)第一个匹配的元素按选择器多个document.querySelectorAll(.msg)NodeList伪数组按标签名document.getElementsByTagName(p)HTMLCollection动态按类名document.getElementsByClassName(msg)HTMLCollection动态特殊元素document.body/document.documentElement直接拿实际开发里querySelectorquerySelectorAll这对就够了——它们接受任何 CSS 选择器规则你早就熟了constboxdocument.querySelector(#box);// 一个元素constfirstMsgdocument.querySelector(.msg);// 第一个 .msgconstallMsgdocument.querySelectorAll(.msg);// 全部 .msg伪数组三个必须知道的细节querySelectorAll返回的是伪数组NodeList有forEach但没有map/filter。要转成真数组Array.from(allMsg)。找不到不报错返回null。后续代码对null取属性会直接抛TypeError: Cannot read properties of null。控制台看到这个报错第一反应应该是「元素没拿到」去检查选择器写没写对、脚本是不是跑在元素之前。老的getElementsByTagName返回的是动态集合——页面新增一个p集合长度会自动变querySelectorAll是静态快照。混着用容易出诡异 bug统一用后者省心。三、事件绑定给页面装上「耳朵」3.1 两种写法推荐一种// 写法一DOM 属性on 开头。简单但同一事件只能绑一个处理函数btn.onclickfunction(){alert(被点了);};// 写法二addEventListener。推荐可绑多个还能控制捕获阶段btn.addEventListener(click,function(){console.log(第一次绑定);});btn.addEventListener(click,function(){console.log(第二次绑定);// 两次绑定都会执行});写法一再赋值一次前一个处理函数就被覆盖了——这是它唯一的但很致命的缺点。统一用addEventListener除非要兼容 IE9 以下2026 年不必了。常用事件类型先记这几个覆盖八成场景事件触发时机click点击input输入框内容变化每次敲键都触发change内容变化且失焦后mouseover/mouseout移入 / 移出会冒泡mouseenter/mouseleave移入 / 移出不冒泡做悬浮菜单用这对keydown/keyup键盘按下 / 抬起focus/blur获得焦点 / 失去焦点mouseover和mouseenter的区别是经典坑给父元素绑mouseover鼠标移到子元素上也会触发因为冒泡。做「移入显示菜单」用mouseenter就不会闪。3.2 事件对象处理函数的入参btn.addEventListener(click,function(e){console.log(e.type);// click 事件类型console.log(e.target);// 实际被点的那个元素console.log(e.clientX,e.clientY);// 鼠标坐标// e.preventDefault(); // 阻止默认行为比如让 a 不跳转、表单不提交// e.stopPropagation(); // 阻止冒泡});e.target和this或e.currentTarget的区别值得单独记e.target是实际被点击的元素this是绑定事件的元素。事件委托全靠这个区别见第五节。四、节点操作动态改页面4.1 改内容与改样式// 内容innerText 保留样式写法textContent 性能更好、拿到的是原始文本box.innerTextb你好/b;// 页面显示字面的 b你好/b不解析标签box.innerHTMLb你好/b;// 解析为 HTML显示加粗的你好// 样式行内样式用 style属性名驼峰化box.style.backgroundColorskyblue;box.style.width300px;// 别忘了单位// 类名改类名比逐条改 style 干净样式写在 CSS 里box.classNamecontainer active;// 覆盖式box.classList.add(active);// 追加box.classList.toggle(active);// 有则删、无则加做开关最合适改样式优先classList.toggle一个开关函数都不用写// 点击按钮深色模式开/关themeBtn.addEventListener(click,(){document.body.classList.toggle(dark);});4.2 创建、添加、删除节点// 创建constlidocument.createElement(li);li.innerText橘子;// 添加list.appendChild(li);// 加到末尾list.insertBefore(li,list.firstChild);// 加到最前// 删除子元素通过父元素删list.removeChild(li);li.remove();// 现代写法自己删自己// 克隆参数 true 表示连子元素一起克隆constcopyli.cloneNode(true);list.appendChild(copy);4.3 一个小需求串起三步点击按钮追加一条弹幕constinputdocument.querySelector(#danmaku-input);constwalldocument.querySelector(#wall);constsendBtndocument.querySelector(#send);sendBtn.addEventListener(click,(){consttextinput.value.trim();// 1. 拿到输入trim 防空格if(!text)return;// 2. 空内容直接拦下constspandocument.createElement(span);// 3. 建节点 → 装内容 → 挂上去span.innerTexttext;span.classNamedanmaku-item;wall.appendChild(span);input.value;// 4. 清空输入框体验细节});四步是固定节奏取值 → 校验 → 建节点挂上 → 收尾。评论区、待办清单、聊天窗口全是这个节奏。五、事件委托一个监听器管一万个子元素场景ul里有 100 个li点击任何一个都要高亮。给每个li绑一次绑定 100 次后面动态新增的li还得再绑。利用冒泡子元素的事件会冒泡到父元素在父元素上统一监听用e.target区分到底点的谁。list.addEventListener(click,function(e){// 只处理点在 li 上的情况点在 ul 空白处不响应if(e.target.tagNameLI){e.target.style.colorred;}});// 之后动态新增的 li 同样生效——因为它点完也会冒泡到 listconstlidocument.createElement(li);li.innerText新来的;list.appendChild(li);两个好处监听器从 100 个变 1 个动态新增的元素自动被覆盖不需要重新绑定。凡是「列表 动态增删」的场景都应该用委托而不是循环绑定。六、常见坑清单都是实际会撞上的脚本写在 head 里拿到 null。解决脚本放/body前或者给document加DOMContentLoaded监听再执行初始化代码。onclick被覆盖。统一addEventListener。mouseover在父子间来回触发。悬浮类交互换mouseenter/mouseleave。innerHTML拼接用户输入。有 XSS 注入风险——用户输入img srcx onerroralert(1)就执行了。展示用户内容用innerText。循环里var i 事件回调回调里i全是最终值。用let块级作用域或者事件委托问题不存在。定时器里的this普通函数的this不指向元素箭头函数或提前存const that this解决。七、完整小页面把本篇三件事全部用上保存为todo.html双击运行!DOCTYPEhtmlhtmlheadmetacharsetutf-8title迷你待办清单/titlestylebody{font-family:sans-serif;max-width:420px;margin:40px auto;}li{padding:6px 0;border-bottom:1px dashed #ddd;cursor:pointer;}li.done{color:#aaa;text-decoration:line-through;}#tip{color:#c00;font-size:13px;min-height:18px;}/style/headbodyh2迷你待办清单/h2inputidinputplaceholder输入事项回车添加buttonidadd添加/buttonspanidtip/spanulidlist/ulp点击条目切换完成状态点击“×”删除/pscriptconstinputdocument.querySelector(#input);constaddBtndocument.querySelector(#add);constlistdocument.querySelector(#list);consttipdocument.querySelector(#tip);functionaddItem(){consttextinput.value.trim();if(!text){tip.innerText内容不能为空;return;}tip.innerText;constlidocument.createElement(li);li.innerHTMLspan classtxt/spanbutton classdel×/button;li.querySelector(.txt).innerTexttext;// 用 innerText 塞用户输入防 XSSlist.appendChild(li);input.value;input.focus();// 收尾细节焦点还给输入框}addBtn.addEventListener(click,addItem);// 回车也能添加键盘事件绑在输入框上input.addEventListener(keydown,function(e){if(e.keyEnter)addItem();});// 事件委托完成状态切换list.addEventListener(click,function(e){if(e.target.classList.contains(del)){e.target.parentElement.remove();}elseif(e.target.classList.contains(txt)){e.target.parentElement.classList.toggle(done);}});/script/body/html数一下获取元素、事件绑定、keydown与click、事件委托、节点创建与删除、classList.toggle、空值校验、XSS 规避——本篇所有知识点都在这 60 行里。