JS DOM操作与事件处理从入门到实践
对于前端开发者来说,让静态的 HTML 页面变得生动、可交互是核心技能之一。实现这一切的关键在于理解和运用文档对象模型 (DOM) 以及 JavaScript 的事件处理机制。本文将带你深入浅出地探索 DOM 操作的奥秘,并掌握JavaScript 事件处理的方方面面。
目录
Part 1: 文档对象模型 (DOM) 揭秘
1. 什么是 DOM?
2. DOM 树结构
Part 2: 核心 DOM 操作:查询、创建、修改、删除
1. 查询 DOM 元素 ("查")
2. 创建新 DOM 元素 ("增")
3. 添加/插入 DOM 元素 ("增")
4. 修改 DOM 元素 ("改")
5. 删除 DOM 元素 ("删")
6. 替换 DOM 元素 ("改")
Part 3: JavaScript 事件处理机制
1. 什么是事件?
2. 事件监听器
3. 事件对象
4. 事件流:捕获与冒泡的奇妙旅程
5. 事件委托/代理:聪明的事件管理员
6. 常见事件类型
Part 4: 实践与最佳做法:写出高效、优雅的 DOM 与事件代码
1. 提升性能的考量
2. 提升代码组织与可读性
3. 关注可访问性 (Accessibility - a11y)
Part 1: 文档对象模型 (DOM) 揭秘
1. 什么是 DOM?
DOM (Document Object Model) 即文档对象模型,是浏览器为 HTML 或 XML 文档创建的一个编程接口。简单来说,当浏览器加载一个 HTML 页面时,它会解析 HTML 代码并创建一个树状的数据结构来表示这个页面——这个树就是 DOM。
- 桥梁作用: DOM 是 HTML 文档与 JavaScript 之间沟通的桥梁。JavaScript 不能直接理解原始的 HTML 文本,但它可以理解和操作 DOM 这个对象集合。
- 树形结构: DOM 将文档中的每一个部分(如元素、文本、注释等)都视为一个节点 (Node)。这些节点按照它们在 HTML 中的层级关系组织起来,形成一个树形结构。
- 节点类型: 主要有元素节点 (Element Node)、文本节点 (Text Node)、属性节点 (Attribute Node) 和注释节点 (Comment Node)。我们最常打交道的是元素节点和文本节点。
2. DOM 树结构
想象一下你的 HTML 文档是一棵家谱树:
<html>
标签是树根。<head>
和<body>
是<html>
的直接子节点。<body>
内部的<div>
,<p>
,<ul>
等元素是其子节点,它们之间也可能存在父子或兄弟关系。
document
对象是整个 DOM 树的入口点,代表了整个文档。我们可以通过 document
对象来访问和操作页面上的任何元素。
HTML 示例:
<html><head><title>我的页面</title></head><body><header><h1>欢迎!</h1></header><main><p>这是一个段落。</p><button id="myButton">点我</button></main></body>
</html>DOM 树 (简化表示):
document└── html├── head│ └── title│ └── "我的页面" (文本节点)└── body├── header│ └── h1│ └── "欢迎!" (文本节点)└── main├── p│ └── "这是一个段落。" (文本节点)└── button (id="myButton")└── "点我" (文本节点)
Part 2: 核心 DOM 操作:查询、创建、修改、删除
JavaScript 提供了丰富的 API 来对 DOM 进行增、删、改、查操作。
1. 查询 DOM 元素 ("查")
获取页面上已存在的元素是进行后续操作的第一步。
document.getElementById('id')
: 通过元素的id
属性获取单个元素。ID 在文档中应该是唯一的。const myButton = document.getElementById('myButton'); console.log(myButton); // 输出 <button id="myButton">...</button>
document.getElementsByClassName('className')
: 通过元素的class
名称获取一个HTMLCollection (元素的集合)。const items = document.getElementsByClassName('list-item'); // 假设页面有多个 class="list-item" 的元素 for (let i = 0; i < items.length; i++) {console.log(items[i]); }
document.getElementsByTagName('tagName')
: 通过元素的标签名 (如'p'
,'div'
) 获取一个HTMLCollection。const paragraphs = document.getElementsByTagName('p'); console.log(paragraphs.length); // 输出页面中 <p> 元素的数量
document.querySelector('cssSelector')
: 使用 CSS 选择器语法获取匹配到的第一个元素。非常强大和灵活。const firstListItem = document.querySelector('ul li.active'); // 获取 ul 下第一个 class 为 active 的 li const header = document.querySelector('#main-header h1'); console.log(header);
document.querySelectorAll('cssSelector')
: 使用 CSS 选择器语法获取匹配到的所有元素,返回一个NodeList (静态的节点集合)。
注意:const allLinks = document.querySelectorAll('a[target="_blank"]'); // 获取所有 target="_blank" 的链接 allLinks.forEach(link => {console.log(link.href); });
HTMLCollection
是动态的,意味着如果文档结构发生变化,它会实时反映这些变化。NodeList
(由querySelectorAll
返回的) 通常是静态的,它是在查询那一刻的快照。HTMLCollection
和NodeList
都可以通过索引访问元素,并且有length
属性,但NodeList
可以使用forEach
方法,而HTMLCollection
通常需要转换成数组 (Array.from()
) 后才能使用forEach
。
2. 创建新 DOM 元素 ("增")
我们可以动态地创建新的 HTML 元素。
document.createElement('tagName')
: 创建指定标签名的新元素节点。const newDiv = document.createElement('div'); const newParagraph = document.createElement('p');
document.createTextNode('text')
: 创建一个包含指定文本的文本节点。const paragraphText = document.createTextNode('这是新段落的内容。');
document.createDocumentFragment()
: 创建一个文档片段。这是一个轻量级的 DOM 容器,当你需要一次性向 DOM 中添加多个元素时,先将它们添加到 DocumentFragment 中,然后再将 DocumentFragment 一次性添加到主 DOM 树,这样可以减少页面重绘和回流,提高性能。const fragment = document.createDocumentFragment(); const item1 = document.createElement('li'); item1.textContent = '列表项1'; const item2 = document.createElement('li'); item2.textContent = '列表项2';fragment.appendChild(item1); fragment.appendChild(item2); // 稍后可以将 fragment 添加到 ul 元素中
3. 添加/插入 DOM 元素 ("增")
创建元素后,需要将它们插入到 DOM 树的特定位置。
parentNode.appendChild(childNode)
: 将childNode
作为parentNode
的最后一个子节点添加。const parentDiv = document.getElementById('parent'); const newChild = document.createElement('p'); newChild.textContent = '我是新来的子节点。'; parentDiv.appendChild(newChild);
parentNode.insertBefore(newNode, referenceNode)
: 在parentNode
的子节点referenceNode
之前插入newNode
。如果referenceNode
为null
,则其行为类似appendChild
。const parent = document.getElementById('listContainer'); const newItem = document.createElement('li'); newItem.textContent = '新列表项 (插入)'; const firstItem = parent.querySelector('li'); // 假设已有列表项 if (firstItem) {parent.insertBefore(newItem, firstItem); } else {parent.appendChild(newItem); // 如果没有参考节点,则添加到末尾 }
- 现代插入方法 (更便捷):
element.append(...nodesOrDOMStrings)
: 在element
的子节点的末尾插入一个或多个节点或 DOM 字符串。element.prepend(...nodesOrDOMStrings)
: 在element
的子节点的开头插入。element.before(...nodesOrDOMStrings)
: 在element
之前插入。element.after(...nodesOrDOMStrings)
: 在element
之后插入。
const container = document.getElementById('container'); const p1 = document.createElement('p'); p1.textContent = '段落1 (append)'; const p2 = document.createElement('p'); p2.textContent = '段落2 (prepend)';container.append(p1, '一些文本'); // 可以同时添加节点和文本 container.prepend(p2);
4. 修改 DOM 元素 ("改")
获取或创建元素后,我们可以修改其内容、属性和样式。
-
修改内容:
element.textContent
: 获取或设置元素的纯文本内容。它会自动转义 HTML 标签,所以相对安全,性能也较好。const greeting = document.getElementById('greeting'); greeting.textContent = '你好,世界!'; // 设置文本 console.log(greeting.textContent); // 获取文本
element.innerHTML
: 获取或设置元素的 HTML 内容。可以用来插入 HTML 结构,但要注意 XSS (跨站脚本) 风险。只应在内容来源可信或已做适当清理时使用。const contentDiv = document.getElementById('content'); contentDiv.innerHTML = '<strong>加粗文本</strong> 和一个 <a href="#">链接</a>';
element.innerText
: 与textContent
类似,但它会考虑元素的CSS样式(例如display: none
的元素内容不会被获取),并且在设置时会触发重排。通常推荐使用textContent
。
-
修改属性 :
element.getAttribute('attrName')
: 获取指定属性的值。element.setAttribute('attrName', 'value')
: 设置或更新指定属性的值。element.hasAttribute('attrName')
: 检查元素是否拥有指定属性。element.removeAttribute('attrName')
: 移除指定属性。const myImage = document.getElementById('myImage'); myImage.setAttribute('src', 'new_image.jpg'); myImage.setAttribute('alt', '一张新图片'); console.log(myImage.getAttribute('src')); // new_image.jpg if (myImage.hasAttribute('data-custom')) {myImage.removeAttribute('data-custom'); }
- 直接属性访问: 对于标准的 HTML 属性,通常可以直接作为元素的 JavaScript 对象属性来访问和修改,如
element.id
,element.className
,element.src
,element.href
,element.value
(对于表单元素)。
属性 (Attributes) vs 特性 (Properties): HTML 标签上写的叫属性 (attribute),JS 对象上的叫特性 (property)。多数情况下它们是同步的,但有些例外 (如const link = document.querySelector('a'); link.href = 'https://www.example.com'; link.id = 'mySpecialLink';
input
的value
attribute 反映初始值,而value
property 反映当前值)。
-
修改样式 :
element.style.property = 'value'
: 直接修改元素的内联样式。CSS 属性名需要转换为驼峰式命名 (如background-color
变为backgroundColor
)。const box = document.getElementById('box'); box.style.width = '200px'; box.style.height = '200px'; box.style.backgroundColor = 'lightblue'; box.style.border = '1px solid blue';
element.className
: 获取或设置元素的class
属性字符串。设置会覆盖所有现有类名。const message = document.getElementById('message'); message.className = 'alert success'; // 设置多个类
element.classList
(推荐): 提供更方便的方法来操作类名,返回一个DOMTokenList
。add('className1', 'className2', ...)
: 添加一个或多个类名。remove('className1', 'className2', ...)
: 移除一个或多个类名。toggle('className', forceBoolean?)
: 切换类名。如果存在则移除,不存在则添加。可选的forceBoolean
参数可以强制添加或移除。contains('className')
: 检查是否存在指定类名。
const panel = document.getElementById('infoPanel'); panel.classList.add('visible', 'highlight'); panel.classList.remove('hidden'); if (panel.classList.contains('highlight')) {console.log('Panel is highlighted.'); } panel.classList.toggle('expanded');
getComputedStyle(element).property
: 获取元素最终计算后的样式值(包括来自外部CSS文件、内部样式表和内联样式的综合结果)。返回的是只读的。const styledElement = document.getElementById('styledElement'); const computedColor = window.getComputedStyle(styledElement).color; console.log('Computed color:', computedColor);
5. 删除 DOM 元素 ("删")
parentNode.removeChild(childNode)
: 从parentNode
中移除指定的childNode
。被移除的节点仍然存在于内存中,可以被重新添加到文档中。const list = document.getElementById('myList'); const itemToRemove = document.getElementById('item-2'); if (list && itemToRemove && itemToRemove.parentNode === list) { // 确保是其子节点list.removeChild(itemToRemove); }
- 现代方法:
element.remove()
: 直接从其父节点中移除该元素自身。更简洁。const oldAd = document.getElementById('oldAdBanner'); if (oldAd) {oldAd.remove(); }
6. 替换 DOM 元素 ("改")
parentNode.replaceChild(newNode, oldNode)
: 用newNode
替换parentNode
中的子节点oldNode
。const container = document.getElementById('contentArea'); const oldParagraph = container.querySelector('p.old-text'); const newParagraph = document.createElement('p'); newParagraph.textContent = '这是更新后的内容。'; newParagraph.className = 'new-text';if (container && oldParagraph) {container.replaceChild(newParagraph, oldParagraph); }
Part 3: JavaScript 事件处理机制
事件是用户与网页交互(如点击按钮、输入文本)或浏览器自身状态改变(如页面加载完成)时发出的信号。JavaScript 允许我们“监听”这些事件,并在事件发生时执行特定的代码。
1. 什么是事件?
- 用户行为:
click
(点击),keydown
(按下键盘),mousemove
(鼠标移动),submit
(表单提交) 等。 - 浏览器行为:
load
(页面或资源加载完成),DOMContentLoaded
(DOM树构建完成),error
(资源加载错误) 等。 - 事件驱动编程: JavaScript 在浏览器中的很多行为都是基于事件驱动的,即代码的执行是由特定事件的发生来触发的。
2. 事件监听器
我们通过事件监听器来响应事件。
-
HTML 内联属性 (不推荐)
<button onclick="alert('按钮被点击了!')">点我 (内联)</button>
这种方式将 JavaScript 代码直接写在 HTML 中,不利于代码分离和维护。
-
DOM0 级事件处理:
将一个函数赋值给元素的事件处理属性 (如 onclick, onmouseover)
const button0 = document.getElementById('btn0'); button0.onclick = function() {console.log('DOM0 级事件:按钮被点击!'); }; // 再次赋值会覆盖之前的 // button0.onclick = function() { console.log('新的点击处理'); };
缺点是每个事件类型只能绑定一个处理函数。
-
DOM2 级事件处理 (addEventListener - 推荐):
这是现代 Web 开发中最常用和推荐的方式。
- 语法:
element.addEventListener('eventType', handlerFunction, useCaptureOrOptionsObject)
eventType
: 事件类型字符串,如'click'
,'mouseover'
,'keydown'
(不需要on
前缀)。handlerFunction
: 事件发生时要执行的函数。useCaptureOrOptionsObject
:- 布尔值:
true
表示在捕获阶段处理事件,false
(默认) 表示在冒泡阶段处理。 - 对象:可以传递更详细的选项,如
{ capture: true, once: true, passive: true }
。once: true
: 处理函数最多执行一次后自动移除。passive: true
: 向浏览器表明处理函数不会调用event.preventDefault()
,有助于优化滚动等性能。
- 布尔值:
- 移除监听器:
element.removeEventListener('eventType', handlerFunction, useCaptureOrBoolean)
- 重要: 移除时传入的
handlerFunction
必须与添加时传入的函数是同一个引用。匿名函数无法通过这种方式移除。
- 重要: 移除时传入的
const button2 = document.getElementById('btn2');function handleClick() {console.log('DOM2 级事件:按钮被点击!'); } function anotherClickHandler() {console.log('DOM2 级事件:按钮也被另一个函数处理了!') }button2.addEventListener('click', handleClick); button2.addEventListener('click', anotherClickHandler); // 可以为同一事件添加多个处理函数// 移除 handleClick // button2.removeEventListener('click', handleClick);
- 语法:
3. 事件对象
当事件发生时,浏览器会自动创建一个包含事件相关信息的对象,并将其作为参数传递给事件处理函数。通常我们将其命名为 event
或 e
。
-
常用属性:
event.type
: (字符串) 事件的类型,如'click'
。event.target
: (元素节点) 实际触发事件的那个元素(事件源)。event.currentTarget
: (元素节点) 事件监听器当前附加到的那个元素。在事件冒泡过程中,target
是不变的,而currentTarget
会是路径上每个正在处理事件的元素。event.bubbles
: (布尔值) 事件是否冒泡。event.cancelable
: (布尔值) 事件是否可以被取消默认行为。
-
常用方法:
event.preventDefault()
: 阻止事件的默认浏览器行为。例如,阻止链接的跳转,阻止表单的提交。event.stopPropagation()
: 停止事件在 DOM 树中的进一步传播(冒泡或捕获)。
-
特定事件类型的属性:
- 鼠标事件 :
event.clientX
,event.clientY
: 鼠标指针相对于浏览器窗口可视区域的坐标。event.pageX
,event.pageY
: 鼠标指针相对于整个文档的坐标。event.button
: 按下的鼠标按钮 (0: 左键, 1: 中键, 2: 右键)。event.altKey
,event.ctrlKey
,event.shiftKey
,event.metaKey
(Mac上的Command键): 布尔值,表示相应的功能键是否被按下。
- 键盘事件 :
event.key
: 按下的键的字符值 (如'a'
,'Enter'
,'ArrowUp'
)。推荐使用。event.code
: 按下的物理键的代码 (如'KeyA'
,'Enter'
,'ArrowUp'
),不受键盘布局影响。event.altKey
,event.ctrlKey
,event.shiftKey
,event.metaKey
: 同上。
const myLink = document.getElementById('myLink'); myLink.addEventListener('click', function(event) {event.preventDefault(); // 阻止链接默认的跳转行为console.log('链接被点击,但默认行为已阻止。');console.log('事件类型:', event.type); // "click"console.log('目标元素:', event.target); // <a> 元素console.log('当前目标:', event.currentTarget); // <a> 元素 });const inputField = document.getElementById('textInput'); inputField.addEventListener('keydown', function(e) {console.log(`按下的键: ${e.key}, 物理键码: ${e.code}`);if (e.key === 'Enter') {console.log('回车键被按下!');// e.preventDefault(); // 如果在表单中,可能需要阻止默认提交} });
- 鼠标事件 :
4. 事件流:捕获与冒泡的奇妙旅程
想象一下,当你在网页上的一个按钮上点击时,这个“点击”的信号并不仅仅是按钮自己知道,它其实经历了一场从“天”到“地”,再从“地”到“天”的旅程。这个旅程就是事件流,它描述了事件在 DOM 树中传播的顺序。
事件流主要包含三个阶段:
-
捕获阶段 :圣旨下达
- 当事件发生时,它首先像一道“圣旨”从最高层(
window
对象)开始,沿着 DOM 树的层级关系,由外向内,一层层向下传播。它会经过目标元素的祖先元素(比如包含按钮的div
,再往上的body
等),一直“下达”到实际触发事件的那个元素(我们称之为“目标元素”,比如你点击的那个按钮event.target
)。 - 在这个阶段,如果你给沿途的父元素设置了“在捕获阶段就处理”的监听器,它们就能提前“截获”并处理这个事件,就像大臣们在圣旨到达最终目的地前就能阅览一样。
- 当事件发生时,它首先像一道“圣旨”从最高层(
-
目标阶段 :正主响应
- “圣旨”(事件)终于到达了它的最终目的地——目标元素(比如那个被点击的按钮)。
- 浏览器会检查这个目标元素本身是否注册了针对该事件的监听器。如果注册了,就会执行相应的处理函数。此时,无论是设置为捕获阶段还是冒泡阶段(默认)的监听器,只要是绑定在目标元素上的,都会在这个阶段被触发(具体顺序取决于绑定时的设置和浏览器实现,但通常捕获优先于冒泡)。
-
冒泡阶段:消息上报
- 在目标元素处理完事件(或者即使没有处理)之后,这个事件通常不会就此消失。它会像水中的气泡一样,从目标元素开始,沿着 DOM 树的层级关系,由内向外,一层层向上“冒泡”传播。它会再次经过目标元素的父元素、祖父元素,一直到文档的根节点,最终回到
window
对象。 - 这是大多数事件(如
click
,mouseover
等)的默认行为。如果你给沿途的父元素设置了“在冒泡阶段处理”(这是addEventListener
的默认情况)的监听器,它们就能在这个事件“回程”的阶段响应事件,就像地方官员逐级向上传达消息一样。
- 在目标元素处理完事件(或者即使没有处理)之后,这个事件通常不会就此消失。它会像水中的气泡一样,从目标元素开始,沿着 DOM 树的层级关系,由内向外,一层层向上“冒泡”传播。它会再次经过目标元素的父元素、祖父元素,一直到文档的根节点,最终回到
如何在代码中控制?
addEventListener
方法的第三个参数用来精确控制监听器在哪个阶段触发:
element.addEventListener('click', myFunction, true);
将第三个参数设置为true
,则myFunction
会在捕获阶段执行。element.addEventListener('click', myFunction, false);
(或者不写第三个参数,因为默认值就是false
),则myFunction
会在冒泡阶段执行。
代码示例:
<div id="grandparent" style="padding: 30px; background-color: lightblue;">祖父<div id="parent" style="padding: 20px; background-color: lightgreen;">父亲<button id="child" style="padding: 10px; background-color: yellow;">孩子 </button></div>
</div>
const grandparent = document.getElementById('grandparent');
const parent = document.getElementById('parent');
const child = document.getElementById('child');// --- 捕获阶段监听 ---
grandparent.addEventListener('click', function(event) {console.log(`捕获阶段:监听器在 ${this.id},事件目标是 ${event.target.id}`);
}, true);parent.addEventListener('click', function(event) {console.log(`捕获阶段:监听器在 ${this.id},事件目标是 ${event.target.id}`);
}, true);child.addEventListener('click', function(event) {// 对于目标元素,捕获阶段的监听器会在目标阶段早期被触发console.log(`捕获/目标阶段:监听器在 ${this.id},事件目标是 ${event.target.id}`);
}, true);// --- 冒泡阶段监听 (默认) ---
grandparent.addEventListener('click', function(event) {console.log(`冒泡阶段:监听器在 ${this.id},事件目标是 ${event.target.id}`);
});parent.addEventListener('click', function(event) {console.log(`冒泡阶段:监听器在 ${this.id},事件目标是 ${event.target.id}`);// 如果在这里调用 event.stopPropagation(); 下面的 grandparent 的冒泡监听就不会执行了// event.stopPropagation();
});child.addEventListener('click', function(event) {// 对于目标元素,冒泡阶段的监听器会在目标阶段晚期(或紧随其后)被触发console.log(`冒泡/目标阶段:监听器在 ${this.id},事件目标是 ${event.target.id}`);
});console.log("--- 请点击 '孩子' 按钮 ---");
当你点击 "孩子 " 按钮后,控制台大致的输出顺序会清晰地展示事件流的路径——先从外向内(捕获),到达目标,再从内向外(冒泡):
捕获阶段:监听器在 grandparent,事件目标是 child
捕获阶段:监听器在 parent,事件目标是 child
捕获/目标阶段:监听器在 child,事件目标是 child
冒泡/目标阶段:监听器在 child,事件目标是 child
冒泡阶段:监听器在 parent,事件目标是 child
冒泡阶段:监听器在 grandparent,事件目标是 child
阻止事件传播:event.stopPropagaton()
如果你不希望事件在当前元素处理后,继续向上冒泡(或者在捕获阶段继续向下传播),可以在事件处理函数中调用 event.stopPropagation()
。这就好比在消息传递的某个环节说:“消息到此为止,不用再往其他地方传了!”这在某些情况下可以避免父元素上不必要的事件处理。
5. 事件委托/代理:聪明的事件管理员
想象一个场景:你有一个很长的待办事项列表 (<ul>
),列表中的每一项 (<li>
) 点击后都需要标记为完成。如果列表有100项,难道我们要给这100个 <li>
元素都单独绑定一个点击事件监听器吗?
这样做会有两个主要问题:
- 性能开销: 绑定大量的事件监听器会占用更多的内存和CPU资源,尤其是在元素非常多的时候。
- 动态元素的烦恼: 如果你通过JavaScript动态地向列表中添加了新的待办事项,新添加的这些
<li>
元素是不会自动拥有之前绑定的点击事件的,你还得手动为它们再绑定一次,非常麻烦。
事件委托 就是来解决这些问题的“聪明管理员”!
核心思想:
与其给每个子元素都派一个“警卫”(事件监听器),不如只在它们的共同父元素(比如 <ul>
)上派一个“总警卫”。利用事件冒泡的原理(还记得吗?事件会从目标元素向上传播),当任何一个子元素(比如某个 <li>
)被点击时,这个点击事件会“冒泡”到父元素 <ul>
上。这时,父元素上的“总警卫”就会被触发。
工作原理:
- 将事件监听器绑定在这些子元素的共同父元素上。
- 当子元素上的事件(如
click
)发生并冒泡到父元素时,父元素的监听器被触发。 - 在父元素的监听器函数内部,我们可以通过事件对象
event.target
来判断究竟是哪个子元素真正触发了这个事件。event.target
指向的是实际被点击的那个最具体的元素(也就是我们的“消息来源”)。
优点:
- 高效: 只需要一个事件监听器,大大减少了内存占用和初始设置的开销。
- 灵活: 对于动态添加的子元素(比如用户新加的待办事项),无需重新绑定事件,它们产生的事件同样会冒泡到父元素,被统一处理,非常方便。
- 简洁: 代码管理更方便,逻辑更集中。
代码示例:
<ul id="todoList" style="border:1px solid #ccc; padding: 10px;"><li>学习 JavaScript</li><li>做个练习项目</li><li>喝杯水</li>
</ul>
<button id="addTodoBtn">添加待办事项</button>
<style>.completed {text-decoration: line-through;color: grey;}
</style>
const todoList = document.getElementById('todoList');
const addTodoBtn = document.getElementById('addTodoBtn');// 在父元素 ul 上设置事件监听器
todoList.addEventListener('click', function(event) {// event.target 是用户实际点击的那个元素// 我们只关心被点击的是否是 <li> 元素if (event.target && event.target.tagName === 'LI') {// event.target.tagName 返回的是大写的标签名,如 'LI'// 也可以用更现代的方式 event.target.matches('li')const listItemText = event.target.textContent;console.log(`你点击了待办事项: "${listItemText}"`);// 给被点击的 <li> 添加/移除一个 "completed" 样式类event.target.classList.toggle('completed'); // toggle 会在有和无之间切换// 你也可以在这里做其他操作,比如删除、编辑等}
});// 动态添加新的待办事项
let todoCounter = 3;
addTodoBtn.addEventListener('click', function() {todoCounter++;const newTodo = document.createElement('li');newTodo.textContent = `新的待办 ${todoCounter}`;todoList.appendChild(newTodo);// 注意:新添加的 <li> 无需单独绑定事件,点击它时,父元素 ul 的监听器依然会处理!
});
在这个例子中,无论列表有多少项,或者之后动态添加了多少项,我们都只需要 todoList
(父元素 <ul>
) 上的那一个点击事件监听器。它就像一个聪明的管理员,有效地管理着所有子项的点击事件。
6. 常见事件类型
- 鼠标事件:
click
: 单击。dblclick
: 双击。mousedown
: 鼠标按钮按下。mouseup
: 鼠标按钮释放。mousemove
: 鼠标在元素上移动。mouseover
: 鼠标指针进入元素或其子元素。mouseout
: 鼠标指针离开元素或进入其子元素。mouseenter
: 鼠标指针进入元素 (不冒泡,不关心子元素)。mouseleave
: 鼠标指针离开元素 (不冒泡,不关心子元素)。
- 键盘事件:
keydown
: 按下键盘键。keyup
: 释放键盘键。keypress
: (已不推荐使用) 按下产生字符的键时触发。
- 表单事件:
submit
: 表单提交时 (通常在<form>
元素上监听)。change
: 表单元素内容改变且失去焦点后 (如<input type="text">
,<select>
,<input type="checkbox">
)。input
: 表单元素内容实时改变时 (如<input type="text">
,<textarea>
)。focus
: 元素获得焦点。blur
: 元素失去焦点。
- 窗口/文档事件:
load
: 整个页面及所有资源 (图片、CSS等) 加载完成后在window
上触发。也用于图片等单个资源的加载完成。DOMContentLoaded
: HTML文档被完全加载和解析完成之后触发,无需等待样式表、图像和子框架的完成加载 (在document
上触发)。通常比load
事件更早触发,是执行初始化脚本的好时机。resize
: 浏览器窗口大小改变时在window
上触发。scroll
: 页面或元素滚动时触发。
- 触摸事件 (主要用于移动设备):
touchstart
,touchmove
,touchend
,touchcancel
。
Part 4: 实践与最佳做法:写出高效、优雅的 DOM 与事件代码
掌握了 DOM 操作和事件处理的基础后,如何写出更专业、性能更好、更易于维护的代码呢?以下是一些重要的实践和最佳做法,并配有代码示例帮助理解。
1. 提升性能的考量
频繁或不当的 DOM 操作是导致页面性能下降的常见原因。
-
a. 减少 DOM 操作次数:批量处理是王道
不良示例: 在循环中逐个添加元素到 DOM。
// 假设有一个数组 itemsData = ['苹果', '香蕉', '橙子']; // const myList = document.getElementById('myList');// itemsData.forEach(itemText => { // const listItem = document.createElement('li'); // listItem.textContent = itemText; // myList.appendChild(listItem); // 每次循环都操作DOM,导致多次重绘/回流 // }); // console.log("不推荐:逐个添加列表项");
推荐示例: 使用
DocumentFragment
进行批量添加。DocumentFragment
是一个轻量级的 DOM 容器,你可以先把所有新元素添加到它里面,最后再一次性把DocumentFragment
添加到主 DOM 树,这样只会触发一次重绘/回流。const itemsData = ['苹果', '香蕉', '橙子']; const myList = document.getElementById('myList'); // 假设页面有 <ul id="myList"></ul>if (myList) {const fragment = document.createDocumentFragment(); // 创建文档片段itemsData.forEach(itemText => {const listItem = document.createElement('li');listItem.textContent = itemText;fragment.appendChild(listItem); // 先添加到 fragment});myList.appendChild(fragment); // 一次性添加到真实DOMconsole.log("推荐:使用 DocumentFragment 批量添加列表项"); }
-
b. 避免布局抖动
当你在 JavaScript 中连续地读取元素的布局属性(如
offsetHeight
,offsetTop
,clientWidth
等),然后又立即修改这些可能影响布局的属性,接着又去读取,浏览器可能需要在每次读/写之间强制重新计算布局,这非常耗性能。不良示例: 循环中交替读写布局属性。
// const elements = document.querySelectorAll('.box-item'); // // 假设我们想让每个盒子的宽度等于其高度 // elements.forEach(el => { // // 读取 offsetHeight (导致回流以获取精确值) // const h = el.offsetHeight; // // 修改 width (可能导致重绘/回流) // el.style.width = h + 'px'; // 每次循环都可能触发强制同步布局 // }); // console.log("不推荐:循环中交替读写布局属性");
推荐示例: 先集中读取,再集中写入。
const elements = document.querySelectorAll('.box-item'); // 假设页面有一些 .box-item 元素 const heights = [];// 步骤1: 集中读取所有需要的值 elements.forEach(el => {heights.push(el.offsetHeight); });// 步骤2: 集中写入(修改DOM) elements.forEach((el, index) => {el.style.width = heights[index] + 'px'; }); console.log("推荐:分离读写操作,避免布局抖动");
-
c. 优先使用事件委托
当有大量相似元素需要相同的事件处理时(比如一个长列表的每一项),为每个元素都绑定监听器不如在它们的共同父元素上设置一个监听器。这在 Part 3 的“事件委托”部分已有详细代码演示。
核心优势回顾: 减少内存占用,动态添加的子元素也能自动应用事件处理。
-
d. 合理使用节流与防抖
对于高频触发的事件,如
scroll
(滚动)、resize
(窗口大小调整)、mousemove
(鼠标移动) 以及输入框的input
事件,如果不加控制,事件处理函数会被过度执行,严重影响性能。- 防抖 : 任务频繁触发的情况下,只有当任务触发的间隔超过指定时间时,任务才会执行一次。适用于用户停止操作后才需要响应的场景(如输入框搜索建议)。
- 节流: 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。适用于需要规律性响应的场景(如滚动加载)。
代码演示(概念性用法,假设
debounce
和throttle
函数已定义):// 假设已在别处定义了 debounce 和 throttle 函数 // function debounce(func, delay) { /* ... */ } // function throttle(func, limit) { /* ... */ }// const searchInput = document.getElementById('searchInput'); // if (searchInput && typeof debounce === 'function') { // searchInput.addEventListener('input', debounce(function(event) { // console.log('防抖搜索:', event.target.value); // // 实际的搜索逻辑 // }, 500)); // 用户停止输入500ms后执行 // }// if (typeof throttle === 'function') { // window.addEventListener('scroll', throttle(function() { // console.log('节流处理滚动事件'); // // 实际的滚动处理逻辑,比如判断是否加载更多 // }, 200)); // 每200ms最多执行一次 // } console.log("提示:对于高频事件,务必使用节流或防抖优化。");
(注:完整的 节流和防抖实现较为复杂,这里仅演示调用方式。)
2. 提升代码组织与可读性
-
a. 关注点分离:结构、样式、行为各司其职
尽量将 HTML (结构)、CSS (样式) 和 JavaScript (行为) 分离开。
不良示例 (混合):
-
<!DOCTYPE html> <html> <head><title>混合示例</title> </head> <body><button id="mixedBtn"style="color: red; font-weight: bold; padding: 10px;"onclick="this.textContent = '我被点击了!'; console.log('混合按钮被点击'); alert('弹窗!');">点我看看 (样式和行为都在HTML里)</button></body> </html>
推荐示例 (分离):
<button id="separatedBtn">点我</button> <style> /* 通常CSS在外部文件 */#separatedBtn { color: blue; }#separatedBtn.clicked { font-weight: bold; } </style>
const separatedBtn = document.getElementById('separatedBtn'); if (separatedBtn) {separatedBtn.addEventListener('click', function() {console.log('按钮被点击!');this.textContent = '已点击';this.classList.add('clicked');});console.log("推荐:HTML, CSS, JS 分离,关注点清晰。"); }
-
b. 清晰、有意义的命名
为变量、函数(尤其是事件处理函数)使用能够清晰表达其用途的名称。
不够清晰的命名:
// const b = document.getElementById('btn1'); // function doIt(e) { // // ... // } // b.addEventListener('click', doIt);
更清晰的命名:
const submitFormButton = document.getElementById('userRegistrationSubmitBtn');function handleUserRegistrationSubmit(event) {event.preventDefault(); // 假设是表单提交console.log('处理用户注册表单提交...');// ... 表单验证和提交逻辑 ... }// if (submitFormButton) { // submitFormButton.addEventListener('click', handleUserRegistrationSubmit); // } console.log("推荐:使用清晰的变量和函数命名。");
-
c. 及时移除不再需要的事件监听器
当元素从 DOM 中被移除,或者在单页应用 (SPA) 中组件被销毁时,如果其上绑定的事件监听器没有被移除,可能会导致内存泄漏或意外行为。
代码演示(移除自身监听器并移除元素):
const ephemeralButton = document.createElement('button'); ephemeralButton.textContent = '点我后消失 (并移除监听器)'; document.body.appendChild(ephemeralButton); // 先添加到body才能看到function handleClickAndSelfDestruct() {console.log('临时按钮被点击!');// 关键:在元素移除前,先移除其上的事件监听器ephemeralButton.removeEventListener('click', handleClickAndSelfDestruct);ephemeralButton.remove(); // 从DOM中移除按钮console.log('临时按钮及其监听器已移除。'); }ephemeralButton.addEventListener('click', handleClickAndSelfDestruct);
在复杂的SPA框架中,通常框架本身会提供组件生命周期钩子函数,让你在组件卸载时执行清理操作,包括移除事件监听器。
3. 关注可访问性 (Accessibility - a11y)
确保你通过 JavaScript 创建和控制的交互对所有用户(包括使用辅助技术的用户)都是可访问的。
-
a. 确保键盘可操作性
如果使用非标准 HTML 元素(如
<div>
)来模拟按钮或其他交互控件,需要确保它们可以通过键盘聚焦和操作。代码演示(使
<div>
像按钮一样可操作):const customControl = document.createElement('div'); customControl.textContent = '自定义可操作控件'; customControl.setAttribute('role', 'button'); // 1. 语义化:告诉辅助技术这是一个按钮 customControl.setAttribute('tabindex', '0'); // 2. 使其可通过 Tab键 聚焦 customControl.style.cssText = 'padding: 8px; border: 1px solid grey; display: inline-block; cursor: pointer;'; // 简单样式function handleCustomControlAction() {console.log('自定义控件被激活!');alert('自定义控件激活成功!'); }customControl.addEventListener('click', handleCustomControlAction); customControl.addEventListener('keydown', function(event) {// 3. 允许通过 Enter 或 Space 键激活if (event.key === 'Enter' || event.key === ' ') { // 或者 event.code === 'Space'event.preventDefault(); // 防止空格键滚动页面等默认行为handleCustomControlAction();} });document.body.appendChild(customControl); console.log("提示:自定义交互元素需确保键盘可访问性。");
-
b. 使用 ARIA 属性增强语义
对于通过 DOM 操作创建的复杂或非标准交互组件(如自定义下拉菜单、模态框、选项卡面板等),HTML 原生标签可能无法完全表达其角色、状态和属性。此时,应使用 ARIA (Accessible Rich Internet Applications) 属性来补充这些语义信息。
代码演示(简单的自定义开关状态):
const myToggleSwitch = document.createElement('div'); myToggleSwitch.setAttribute('role', 'switch'); // 角色:这是一个开关 myToggleSwitch.setAttribute('aria-checked', 'false'); // 初始状态:未选中 myToggleSwitch.setAttribute('tabindex', '0'); // 可聚焦 myToggleSwitch.textContent = '关'; myToggleSwitch.style.cssText = 'padding: 5px; border: 1px solid black; display: inline-block; cursor: pointer; user-select: none;';let isSwitchOn = false; function toggleTheSwitch() {isSwitchOn = !isSwitchOn;myToggleSwitch.setAttribute('aria-checked', isSwitchOn.toString());myToggleSwitch.textContent = isSwitchOn ? '开' : '关';myToggleSwitch.style.backgroundColor = isSwitchOn ? 'lightgreen' : 'lightcoral';console.log(`开关状态: ${isSwitchOn ? '开' : '关'}`); }myToggleSwitch.addEventListener('click', toggleTheSwitch); myToggleSwitch.addEventListener('keydown', (event) => {if (event.key === 'Enter' || event.key === ' ') {event.preventDefault();toggleTheSwitch();} });document.body.appendChild(myToggleSwitch); console.log("提示:复杂自定义组件应使用ARIA属性增强语义。");
ARIA 是一个相对深入的主题,但了解其基本作用和在必要时查阅相关文档非常重要。
相关文章:
JS DOM操作与事件处理从入门到实践
对于前端开发者来说,让静态的 HTML 页面变得生动、可交互是核心技能之一。实现这一切的关键在于理解和运用文档对象模型 (DOM) 以及 JavaScript 的事件处理机制。本文将带你深入浅出地探索 DOM 操作的奥秘,并掌握JavaScript 事件处理的方方面面。 目录 …...
Hive表JOIN性能问
在处理100TB的Hive表JOIN性能问题时,需采用分层优化策略,结合数据分布特征、存储格式和计算引擎特性。以下是系统性优化方案: 1. 数据倾斜优化(Skew Join) 1.1 识别倾斜键 方法:统计JOIN键的分布频率&…...
关键点检测--使用YOLOv8对Leeds Sports Pose(LSP)关键点检测
目录 1. Leeds Sports Pose数据集下载2. 数据集处理2.1 获取标签2.2 将图像文件和标签文件处理成YOLO能使用的格式 3. 用YOLOv8进行训练3.1 训练3.2 预测 1. Leeds Sports Pose数据集下载 从kaggle官网下载这个数据集,地址为link,下载好的数据集文件如下…...
2025年客运从业资格证备考单选练习题
客运从业资格证备考单选练习题 1、从事道路旅客运输活动时,应当采取必要措施保证旅客的人身和财产安全,发生紧急情况时,首先应( )。 A. 抢救财产 B. 抢救伤员 C. 向公司汇报 答案:B 解析:…...
QMK自定义4*4键盘固件创建教程:最新架构详解
QMK自定义4*4键盘固件创建教程:最新架构详解 前言 通过本教程,你将学习如何在QMK框架下创建自己的键盘固件。QMK是一个强大的开源键盘固件框架,广泛用于DIY机械键盘的制作。本文将详细介绍最新架构下所需创建的文件及其功能。 准备工作 在…...
获取conan离线安装包
1、获取conan离线安装包 # apt-get install python3.12-venv pip #缓存的安装存放在/var/cache/apt/archives目录 # mkdir /myenv && cd /myenv #创建虚拟环境目录 # python3 -m venv myenv #创建虚拟环境 # source myenv/bin/activate #激活虚拟环境ÿ…...
【Java ee初阶】网络原理
应用层 由于下面的四层都是系统已经实现好了的,但是应用层是程序员自己写的,因此应用层是程序员最重要的一层。 应用层中,程序员通常需要定义好数据传输格式,调用传输层api(socket api)进行真正的网络通信…...
Makefile中 链接库,同一个库的静态库与动态库都链接了,生效的是哪个库
Makefile中 链接库,同一个库的静态库与动态库都链接了,生效的是哪个库 在 Makefile 中同时链接同一个库的静态库(.a)和动态库(.so)时,具体哪个库生效取决于链接顺序和编译器行为。以下是详细分析…...
【AI提示词】金字塔模型应用专家
提示说明 专业运用金字塔原理优化信息结构与逻辑表达,实现高效精准的思维传达。 提示词 # Role: 金字塔模型应用专家 ## Profile - **language**: 中文/英文 - **description**: 专业运用金字塔原理优化信息结构与逻辑表达,实现高效精准的思维传…...
电子电器架构 --- 车载以太网拓扑
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…...
使用FastAPI微服务在AWS EKS上实现AI会话历史的管理
架构概述 本文介绍如何使用FastAPI构建微服务架构,在AWS EKS上部署两个微服务: 服务A:接收用户提示服务B:处理对话逻辑,与Redis缓存和MongoDB数据库交互 该架构利用AWS ElastiCache(Redis)实现快速响应,…...
Flutter PIP 插件 ---- 为iOS 重构PipController, Demo界面,更好的体验
接上文 Flutter PIP 插件 ---- 新增PipActivity,Android 11以下支持自动进入PIP Mode 项目地址 PIP, pub.dev也已经同步发布 pip 0.0.3,你的加星和点赞,将是我继续改进最大的动力 在之前的界面设计中,还原动画等体验一…...
vue开发用户注册功能
文章目录 一、开发步骤二、效果图三、搭建页面创建views/Login.vue在App.vue中导入Login.vue 四、数据绑定五、表单校验六、访问后端 API 接口,完成注册七、完整的Login.vue代码八、参考资料 一、开发步骤 二、效果图 三、搭建页面 创建views/Login.vue 完整内容在…...
Qt中的RCC
Qt资源系统(Qt resource system)是一种独立于平台的机制,用于在应用程序中传输资源文件。如果你的应用程序始终需要一组特定的文件(例如图标、翻译文件和图片),并且你不想使用特定于系统的方式来打包和定位这些资源,则可以使用Qt资源系统。 最…...
muduo源码解析
1.对类进行禁止拷贝 class noncopyable {public:noncopyable(const noncopyable&) delete;void operator(const noncopyable&) delete;protected:noncopyable() default;~noncopyable() default; }; 2.日志 使用枚举定义日志等级 enum LogLevel{TRACE,DEBUG,IN…...
Qt QCheckBox 使用
1.开发背景 Qt QCheckBox 是勾选组件,具体使用方法可以参考 Qt 官方文档,这里只是记录使用过程中常用的方法示例和遇到的一些问题。 2.开发需求 QCheckBox 使用和踩坑 3.开发环境 Window10 Qt5.12.2 QtCreator4.8.2 4.功能简介 4.1 简单接口 QChec…...
【工具记录分享】提取bilibili视频字幕
F12大法 教程很多 但方法比较统一 例快速提取视频字幕!适用B站、AI字幕等等。好用 - 哔哩哔哩 无脑小工具 哔哩哔哩B站字幕下载_在线字幕解析-飞鱼视频下载助手 把链接扔进去就会自动生成srt文件 需要txt可以配合: SRT转为TXT...
设计模式【cpp实现版本】
文章目录 设计模式1.单例模式代码设计1.饿汉式单例模式2.懒汉式单例模式 2.简单工厂和工厂方法1.简单工厂2.工厂方法 3.抽象工厂模式4.代理模式5.装饰器模式6.适配器模式7.观察者模式 设计模式 1.单例模式代码设计 为什么需要单例模式,在我们的项目设计中&…...
Python数据分析案例74——基于内容的深度学习推荐系统(电影推荐)
背景 之前都是标准的表格建模和时间序列的预测,现在做一点不一样的数据结构的模型方法。 推荐系统一直是想学想做的,以前读研时候想学没多少相关代码,现在AI资源多了,虽然上班没用到这方面的知识,但是还是想熟悉一下…...
C PRIMER PLUS——第8节:字符串和字符串函数
目录 1. 字符串的定义与表示 2. 获取字符串的两种方式 3.字符串数组 4. 字符串输入函数 4.1 gets()(不推荐使用,有缓冲区溢出风险) 4.2 fgets()(推荐使用) 4.3 scanf() 4.4 gets_s()(C11 标准&…...
Dia浏览器:AI驱动浏览网页,究竟怎么样?(含注册申请体验流程)
名人说:博观而约取,厚积而薄发。——苏轼《稼说送张琥》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、Dia浏览器简介1. 什么是Dia浏览器2. 开发背景与公司简介3. 与传统浏览器的区别 …...
milvus+flask山寨复刻《从零构建向量数据库》第7章
常规练手,图片搜索山寨版。拜读罗云大佬著作,结果只有操作层的东西可以上上手。 书中是自己写的向量数据库,这边直接用python拼个现成的milvus向量数据库。 1. 创建一个向量数据库以及对应的相应数据表: # Milvus Setup Argume…...
【大数据技术-HBase-关于Hmaster、RegionServer、Region等组件功能和读写流程总结】
Hmaster的作用 负责命名空间、表的创建和删除等一些DDL操作、region分配和负载均衡,并不参与数据读写,相比与其他大数据组件,如hdfs的namenode,在hbase中,Hmaster的作用是比较弱化的,即使挂掉,也暂时不影响现有表的读写。 RegionServer的作用 一个机器上一个regionse…...
用c语言实现——一个交互式的中序线索二叉树系统,支持用户动态构建、线索化、遍历和查询功能
知识补充:什么是中序线索化 中序遍历是什么 一、代码解释 1.结构体定义 Node 结构体: 成员说明: int data:存储节点的数据值。 struct Node* lchild:该节点的左孩子 struct Node* rchild:该节点的右孩子…...
Pale Moon:速度优化的Firefox定制浏览器
Pale Moon是一款基于Firefox浏览器的定制版浏览器,专为追求速度和性能的用户设计。它使用开放源代码创建,经过高度优化,适用于现代处理器,提供了更快的页面加载速度和更高效的脚本处理能力。Pale Moon不仅继承了Firefox的安全性和…...
广东省省考备考(第七天5.10)—言语:逻辑填空(每日一练)
错题 解析 第一空,搭配“各个环节”,根据“我国已经形成了相对完善的中药质量标准控制体系”可知,横线处应体现“包含”之意,C项“涵盖”指包括、覆盖,D项“囊括”指把全部包罗在内,均与“各个环节”搭配得…...
Gartner《Container发布与生命周期管理最佳实践》学习心得
近日,Gartner发布了《Best Practices for Container Release and Life Cycle Management》, 报告为技术专业人士提供了关于容器发布和生命周期管理的深入指导。这份报告强调了容器在现代应用开发和部署中的核心地位,并提供了一系列最佳实践&…...
内存、磁盘、CPU区别,Hadoop/Spark与哪个联系密切
1. 内存、磁盘、CPU的区别和作用 1.1 内存(Memory) 作用: 内存是计算机的短期存储器,用于存储正在运行的程序和数据。它的访问速度非常快,比磁盘快几个数量级。在分布式计算中,内存用于缓存中间结果、存储…...
SpringCloud之Eureka基础认识-服务注册中心
0、认识Eureka Eureka 是 Netflix 开源的服务发现组件,后来被集成到 Spring Cloud 生态中,成为 Spring Cloud Netflix 的核心模块之一。它主要用于解决分布式系统中服务注册与发现的问题。 Eureka Server 有必要的话,也可以做成集群…...
MySQL 中如何进行 SQL 调优?
在MySQL中进行SQL调优是一个系统性工程,需结合索引优化、查询改写、性能分析工具、数据库设计及硬件配置等多方面策略。以下是具体优化方法及案例说明: 一、索引优化:精准提速的关键 索引类型选择 普通索引:加速频繁查询的列&…...
Linux平台下SSH 协议克隆Github远程仓库并配置密钥
目录 注意:先提前配置好SSH密钥,然后再git clone 1. 检查现有 SSH 密钥 2. 生成新的 SSH 密钥 3. 将 SSH 密钥添加到 ssh-agent 4. 将公钥添加到 GitHub 5. 测试 SSH 连接 6. 配置 Git 使用 SSH 注意:先提前配置好SSH密钥,然…...
Android平台FFmpeg音视频开发深度指南
一、FFmpeg在Android开发中的核心价值 FFmpeg作为业界领先的多媒体处理框架,在Android音视频开发中扮演着至关重要的角色。它提供了: 跨平台支持:统一的API处理各种音视频格式完整功能链:从解码、编码到滤镜处理的全套解决方案灵…...
QSFP+、QSFP28、QSFP-DD接口分别实现40G、100G、200G/400G以太网接口
常用的光模块结构形式: 1)QSFP等效于4个SFP,支持410Gbit/s通道传输,可通过4个通道实现40Gbps传输速率。与SFP相比,QSFP光模块的传输速率可达SFP光模块的四倍,在部署40G网络时可直接使用QSFP光模块…...
MySQL事务和JDBC中的事务操作
一、什么是事务 事务是数据库操作的最小逻辑单元,具有"全有或全无"的特性。以银行转账为例: 典型场景: 从A账户扣除1000元 向B账户增加1000元 这两个操作必须作为一个整体执行,要么全部成功,要么全部失败…...
Linux系统下安装mongodb
1. 配置MongoDB的yum仓库 创建仓库文件 sudo vi /etc/yum.repos.d/mongodb-org.repo添加仓库配置 根据系统版本选择配置(以下示例为CentOS 7和CentOS 9的配置): CentOS 7(安装MongoDB 5.0/4.2等旧版本): In…...
JavaScript篇:async/await 错误处理指南:优雅捕获异常,告别失控的 Promise!
大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了…...
智能时代下,水利安全员证如何引领行业变革?
当 5G、AI、物联网等技术深度融入水利工程,传统安全管理模式正经历颠覆性变革。在这场智能化浪潮中,水利安全员证扮演着怎样的角色?又将如何重塑行业人才需求格局? 水利工程智能化转型对安全管理提出新挑战。无人机巡检、智能监测…...
使用FastAPI和React以及MongoDB构建全栈Web应用03 全栈开发快速入门
一、什么是全栈开发 A full-stack web application is a complete software application that encompasses both the frontend and backend components. It’s designed to interact with users through a web browser and perform actions that involve data processing and …...
NHANES稀有指标推荐:HALP score
文章题目:Associations of HALP score with serum prostate-specific antigen and mortality in middle-aged and elderly individuals without prostate cancer DOI:10.3389/fonc.2024.1419310 中文标题:HALP 评分与无前列腺癌的中老年人血清…...
软考错题集
一个有向图具有拓扑排序序列,则该图的邻接矩阵必定为()矩阵。 A.三角 B.一般 C.对称 D.稀疏矩阵的下三角或上三角部分包含非零元素,而其余部分为零。一般矩阵这个术语太过宽泛,不具体指向任何特定性 质的矩阵。对称矩阵…...
llama.cpp无法使用gpu的问题
使用cuda编译llama.cpp后,仍然无法使用gpu。 ./llama-server -m ../../../../../model/hf_models/qwen/qwen3-4b-q8_0.gguf -ngl 40 报错如下 ggml_cuda_init: failed to initialize CUDA: forward compatibility was attempted on non supported HW warning: n…...
[面试]SoC验证工程师面试常见问题(五)TLM通信篇
SoC验证工程师面试常见问题(五) 摘要:UVM (Universal Verification Methodology) 中的 TLM (Transaction Level Modeling) 通信是一种用于在验证组件之间传递事务(Transaction)的高层次抽象机制。它通过端口(Port)和导出(Export)实现组件间的解耦通信,避免了信…...
Spring循环依赖问题
个人理解,有问题欢迎指正。 Spring 生命周期中,首先使用构造方法对 bean 实例化,实例化完成之后才将不完全的 bean放入三级缓存中提前暴露出 bean,然后进行属性赋值,此时容易出现循环依赖问题。 由此可见,…...
AtCoder Beginner Contest 405(CD)
C - Sum of Product 翻译: 给你一个长为N的序列。 计算的值。 思路: 可使用前缀和快速得到区间和,在遍历 i 即可。(前缀和) 实现: #include<bits/stdc.h> using namespace std; using ll long lon…...
MindSpore框架学习项目-ResNet药物分类-模型优化
目录 5.模型优化 5.1模型优化 6.结语 参考内容: 昇思MindSpore | 全场景AI框架 | 昇思MindSpore社区官网 华为自研的国产AI框架,训推一体,支持动态图、静态图,全场景适用,有着不错的生态 本项目可以在华为云modelar…...
C. scanf 函数基础
scanf 函数 1. scanf 函数基础1.1 函数原型与头文件1.2 格式化输入的基本概念2.1 常见格式说明符整数格式说明符浮点数格式说明符字符和字符串格式说明符其他格式说明符2.2 格式说明符的高级用法宽度修饰符精度修饰符跳过输入字段宽度组合修饰符对齐修饰符实际应用示例3.2 精度…...
《C++探幽:模板从初阶到进阶》
文章目录 :red_circle:一、模板基础:开启泛型编程之门(一)泛型编程的必要性(二)函数模板1. 函数模板概念2. 函数模板定义格式3. 函数模板原理4. 函数模板实例化5. 模板参数匹配原则 (三)类模板1…...
画立方体软件开发笔记 js three 投影 参数建模 旋转相机 @tarikjabiri/dxf导出dxf
gitee: njsgcs/njsgcs_3d mainwindow.js:4 Uncaught SyntaxError: The requested module /3dviewport.js does not provide an export named default一定要default吗 2025-05-10 14-27-58 专门写了个代码画立方体 import{ scene,camera,renderer} from ./3dviewp…...
LVGL图像导入和解码
LVGL版本:8.1 概述 在LVGL中,可以导入多种不同类型的图像: 经转换器生成的C语言数组,适用于页面中不常改变的固定图像。存储系统中的外部图像,比较灵活,可以通过插卡或从网络中获取,但需要配置…...
Win10无法上网:Windows 无法访问指定设备、路径或文件。你可能没有适当的权限访问该项目找不到域 TEST 的域控制器DNS 解析存在问题
目录 一.先看问题 二.解决问题 三.补充备用 一.先看问题 Win08有网且已经加入域 Win10无网并且找不到域(说明:Win10我之前已经加入过域的,并且能够上网,但每次在宿舍和教室切换校园网,就会导致只有Win10无网&#…...