前端 DOM 操作全解析:从原生 JS 到 Vue/React 框架实战 - 详解
DOM(文档对象模型)是前端开发的核心基础,无论是原生开发还是框架应用,都离不开对 DOM 的操作。本文将系统讲解获取 DOM 元素、绑定事件、阻止事件三大核心操作,涵盖原生 JavaScript、Vue、React 三种场景的所有常用方法,并结合实战案例对比分析,帮助你彻底掌握 DOM 操作的精髓。
一、获取 DOM 元素:定位页面元素的艺术
获取 DOM 元素是一切操作的前提,不同环境下的实现方式不同,但核心逻辑都是通过「选择器」或「元素关系」定位目标。
1. 原生 JavaScript:直接操作 DOM 树
原生 JS 提供了丰富的 DOM 选择 API,基于 document 对象或已获取的 DOM 元素调用,可分为「直接选择」和「关系选择」两大类。
(1)直接选择 API(按条件匹配)
| 方法 | 作用 | 返回值 | 特点 |
|---|---|---|---|
getElementById(id) | 通过 ID 选择元素 | 单个 DOM 元素(无匹配则为 null) | 唯一能直接获取单个元素的方法,性能最优(ID 唯一) |
getElementsByClassName(class) | 通过类名选择元素 | HTMLCollection(动态集合) | 实时更新(DOM 变化时自动同步),返回类数组(需转换为数组操作) |
getElementsByTagName(tag) | 通过标签名选择元素 | HTMLCollection(动态集合) | 同上,常用于批量选择同类型标签(如 <div>、<p>) |
querySelector(selector) | 通过 CSS 选择器选择第一个匹配元素 | 单个 DOM 元素(无匹配则为 null) | 支持复杂选择器(如 #app .child > span),最灵活 |
querySelectorAll(selector) | 通过 CSS 选择器选择所有匹配元素 | NodeList(静态集合) | 不实时更新(DOM 变化后需重新查询),支持 forEach 遍历 |
段落1
段落2
子元素
<script>
// 1. 通过 ID 获取
const app = document.getElementById('app');
console.log(app); // ...
// 2. 通过类名获取(动态集合)
const texts = document.getElementsByClassName('text');
console.log(texts.length); // 2(返回 HTMLCollection)
// 转换为数组后使用 forEach
Array.from(texts).forEach(item => console.log(item));
// 3. 通过标签名获取
const paragraphs = document.getElementsByTagName('p');
console.log(paragraphs.length); // 2
// 4. 通过 CSS 选择器获取第一个匹配元素
const firstText = document.querySelector('.text');
console.log(firstText); // 段落1
// 5. 通过 CSS 选择器获取所有匹配元素(静态集合)
const boxes = document.querySelectorAll('.box span');
console.log(boxes); // NodeList [ ]
boxes.forEach(span => console.log(span));
</script>
(2)关系选择 API(按元素层级)
通过已获取的 DOM 元素,利用父子、兄弟关系定位其他元素:
| 方法 | 作用 | 示例 |
|---|---|---|
parentNode | 获取父节点 | child.parentNode |
children | 获取所有子元素(不含文本节点) | parent.children |
firstElementChild / lastElementChild | 获取第一个 / 最后一个子元素 | parent.firstElementChild |
nextElementSibling / previousElementSibling | 获取下一个 / 上一个兄弟元素 | current.nextElementSibling |
实战示例:
const app = document.getElementById('app');
// 获取 app 的第一个子元素
const firstChild = app.firstElementChild; // 段落1
// 获取 firstChild 的下一个兄弟元素
const nextSibling = firstChild.nextElementSibling; // 段落2
// 获取 nextSibling 的父节点(回到 app)
console.log(nextSibling.parentNode === app); // true
2. Vue 框架:通过 ref 引用优雅获取
Vue 倡导「数据驱动视图」,不推荐直接操作 DOM,但必要时可通过 ref 引用安全获取 DOM 元素。Vue 2 和 Vue 3 的用法略有差异,但核心思想一致。
(1)Vue 2 中的 ref 用法
- 步骤:在模板元素上添加
ref属性 → 通过this.$refs.xxx获取 DOM。 - 注意:需在
mounted生命周期钩子中访问(组件挂载后 DOM 才生成)。
<script>
export default {
mounted() {
// 组件挂载后获取 DOM
console.log(this.$refs.container); // ...
console.log(this.$refs.btn); //
},
methods: {
handleClick() {
// 事件中也可访问 ref
this.$refs.btn.textContent = "已点击";
}
}
};
</script>
(2)Vue 3 组合式 API 中的 ref 用法
- 步骤:定义与模板
ref同名的ref变量 → 通过变量.value获取 DOM。 - 注意:需在
onMounted钩子中访问(同 Vue 2 逻辑)。
<script setup>
import { ref, onMounted } from 'vue';
// 定义 ref 变量(与模板 ref 属性同名)
const container = ref(null);
const btn = ref(null);
onMounted(() => {
// 组件挂载后,.value 即为 DOM 元素
console.log(container.value); // ...
console.log(btn.value); //
});
const handleClick = () => {
btn.value.textContent = "已点击";
};
</script>
(3)Vue 中获取 DOM 的注意事项
ref仅能获取当前组件内的 DOM,跨组件需通过$parent或状态管理(如 Pinia)间接传递。- 对于
v-for生成的动态元素,ref会返回一个数组(按遍历顺序对应 DOM 元素):
- {{ i }}
<script setup>
import { ref, onMounted } from 'vue';
const items = ref([]);
onMounted(() => {
console.log(items.value); // [li, li, li](3个列表项)
});
</script>
3. React 框架:useRef 钩子的精准定位
React 同样不鼓励直接操作 DOM,但可通过 ref 机制安全访问。函数组件推荐使用 useRef 钩子,类组件使用 createRef。
(1)函数组件(useRef)
- 步骤:通过
useRef(null)创建 ref 对象 → 绑定到元素的ref属性 → 通过ref.current获取 DOM。 - 注意:
ref.current在组件挂载前为null,需在useEffect(依赖为空数组)中访问。
import { useRef, useEffect } from 'react';
function DOMDemo() {
// 创建 ref 对象
const containerRef = useRef(null);
const btnRef = useRef(null);
// 组件挂载后执行(类似 Vue 的 mounted)
useEffect(() => {
console.log(containerRef.current); // ...
console.log(btnRef.current); //
}, []);
const handleClick = () => {
btnRef.current.textContent = "已点击";
};
return (
);
}
(2)类组件(createRef)
- 步骤:通过
createRef()创建 ref 对象 → 绑定到元素的ref属性 → 通过this.refName.current获取 DOM。 - 注意:需在
componentDidMount生命周期中访问。
import { Component, createRef } from 'react';
class DOMDemo extends Component {
// 创建 ref 对象
containerRef = createRef();
btnRef = createRef();
componentDidMount() {
console.log(this.containerRef.current); // ...
console.log(this.btnRef.current); //
}
handleClick = () => {
this.btnRef.current.textContent = "已点击";
};
render() {
return (
);
}
}
(3)React 中获取动态元素的技巧
对于 map 生成的列表项,需通过「ref 回调函数」批量收集 DOM:
import { useRef, useEffect } from 'react';
function ListDemo() {
const itemsRef = useRef([]);
useEffect(() => {
console.log(itemsRef.current); // [li, li, li](3个列表项)
}, []);
return (
{[1, 2, 3].map((item, index) => (
- (itemsRef.current[index] = el)}
>
{item}
))}
);
}
二、绑定 DOM 事件:交互逻辑的实现
事件绑定是前端交互的核心,不同场景下的绑定方式不同,但最终都是为了让元素响应用户行为(如点击、输入等)。
1. 原生 JavaScript:三种绑定方式对比
原生 JS 绑定事件有三种方式,各有优缺点,需根据场景选择。
(1)HTML 事件属性(不推荐)
直接在 HTML 标签中通过 onxxx 属性绑定事件,将 HTML 与 JS 耦合,维护性差。
<script>
function handleClick() {
console.log('点击了');
}
</script>
(2)DOM 属性绑定(简单场景适用)
通过 JS 给 DOM 元素的 onxxx 属性赋值,同一事件只能绑定一个处理函数(覆盖式)。
const btn = document.getElementById('btn');
// 绑定匿名函数
btn.onclick = function() {
console.log('点击事件1');
};
// 绑定命名函数
function handleClick() {
console.log('点击事件2');
}
btn.onclick = handleClick; // 覆盖上一个事件,只执行这个
// 解绑事件(赋值为 null)
btn.onclick = null;
(3)addEventListener 方法(推荐)
通过 addEventListener 绑定事件,支持同一事件绑定多个处理函数,还可配置事件捕获 / 冒泡阶段触发。
语法:
element.addEventListener(eventType, handler, useCapture);
eventType:事件类型(如'click'、'input',不带on前缀)。handler:事件处理函数。useCapture:布尔值(true表示捕获阶段触发,false表示冒泡阶段,默认false)。
实战示例:
const btn = document.getElementById('btn');
// 绑定第一个处理函数
function handleClick1() {
console.log('点击事件1');
}
btn.addEventListener('click', handleClick1);
// 绑定第二个处理函数(不会覆盖)
function handleClick2() {
console.log('点击事件2');
}
btn.addEventListener('click', handleClick2); // 点击时两个函数都执行
// 解绑事件(需传入原函数引用)
btn.removeEventListener('click', handleClick1); // 只解绑第一个
// 绑定带参数的函数(用箭头函数包裹)
const msg = "自定义消息";
btn.addEventListener('click', () => {
handleClickWithParams(msg);
});
function handleClickWithParams(message) {
console.log(message);
}
常用事件类型:
- 鼠标事件:
click(点击)、dblclick(双击)、mouseenter(鼠标进入)、mouseleave(鼠标离开)。 - 键盘事件:
keydown(按键按下)、keyup(按键抬起)、keypress(按键按压)。 - 表单事件:
input(输入)、change(值改变)、submit(提交)、focus(聚焦)、blur(失焦)。 - 其他:
scroll(滚动)、resize(窗口大小改变)、load(页面加载完成)。
2. Vue 框架:v-on 指令的便捷绑定
Vue 通过 v-on 指令(简写 @)绑定事件,自动处理 DOM 操作,并提供事件修饰符简化常见需求(如阻止冒泡、阻止默认行为)。
(1)基础用法
- 绑定事件:
@事件名="处理函数"。 - 传递参数:通过函数调用传递,
$event可指代原生事件对象。
<script setup>
import { ref } from 'vue';
const count = ref(0);
// 无参处理函数
const handleClick = () => {
console.log('点击了');
};
// 带参处理函数(接收事件对象)
const handleClickWithParams = (msg, e) => {
console.log(msg); // 'hello'
console.log(e); // 原生事件对象
};
</script>
(2)事件修饰符(核心特性)
Vue 提供了一系列事件修饰符,简化事件处理逻辑(无需手动操作 event 对象):
| 修饰符 | 作用 | 等价原生方法 |
|---|---|---|
.stop | 阻止事件冒泡 | e.stopPropagation() |
.prevent | 阻止默认行为 | e.preventDefault() |
.once | 事件只触发一次 | - |
.self | 仅当事件目标是自身时触发 | - |
.capture | 事件在捕获阶段触发 | addEventListener 的 useCapture: true |
.passive | 优化滚动性能(不阻止默认行为) | { passive: true } |
实战示例:
父元素
点我才触发 点我不触发
<script setup>
const handleSubmit = () => {
console.log('表单提交,不刷新页面');
};
const parentClick = () => {
console.log('父元素事件触发');
};
const childClick = () => {
console.log('子元素事件触发'); // 点击子元素,父元素事件不触发(.stop 生效)
};
const handleOnce = () => {
console.log('只触发一次'); // 第二次点击无反应
};
const onlySelf = () => {
console.log('只有点击父元素本身才触发');
};
</script>
(3)按键修饰符(针对键盘事件)
监听键盘事件时,可通过按键修饰符指定特定按键触发:
<script setup>
const handleEnter = (e) => {
console.log('输入内容:', e.target.value);
};
const handleEsc = (e) => {
e.target.value = ''; // 清空输入
};
const handleTab = () => {
console.log('Tab 键被按下');
};
</script>
3. React 框架:驼峰命名的事件绑定
React 通过「驼峰命名」的事件属性(如 onClick、onChange)绑定事件,事件处理函数接收 React 合成事件(SyntheticEvent),但可通过 nativeEvent 访问原生事件对象。
(1)基础用法
- 函数组件:直接在事件属性中绑定处理函数(箭头函数或命名函数)。
- 类组件:通过
this绑定处理函数(需注意this指向问题)。
函数组件示例:
import { useState } from 'react';
function EventDemo() {
const [count, setCount] = useState(0);
// 无参处理函数
const handleClick = () => {
console.log('点击了');
};
// 带参处理函数(用箭头函数包裹传递参数)
const handleClickWithParams = (msg, e) => {
console.log(msg); // 'hello'
console.log(e); // React 合成事件
console.log(e.nativeEvent); // 原生事件对象
};
return (
);
}
类组件示例:
import { Component } from 'react';
class EventDemo extends Component {
state = { count: 0 };
// 用箭头函数绑定 this(推荐)
handleClick = () => {
console.log('点击了');
};
handleClickWithParams = (msg, e) => {
console.log(msg, e.nativeEvent);
};
render() {
return (
);
}
}
(2)React 合成事件 vs 原生事件
- 合成事件:React 封装的跨浏览器事件对象,统一了不同浏览器的事件行为(如
e.target在所有浏览器中一致)。 - 原生事件:可通过
e.nativeEvent获取,对应浏览器原生事件对象。 - 注意:合成事件的
stopPropagation()仅阻止合成事件的冒泡,若同时绑定了原生事件,需用e.nativeEvent.stopImmediatePropagation()阻止所有事件。
(3)事件绑定的 this 指向问题(类组件)
类组件中,若处理函数不是箭头函数,需手动绑定 this,否则 this 会指向 undefined:
class ThisDemo extends Component {
constructor(props) {
super(props);
// 方式1:构造函数中绑定 this
this.handleClick = this.handleClick.bind(this);
}
// 非箭头函数(需手动绑定 this)
handleClick() {
console.log(this); // 指向组件实例
}
render() {
return (
{/* 方式2:渲染时绑定(每次渲染会创建新函数,可能影响性能) */}
{/* 方式3:箭头函数包裹(同上,可能影响性能) */}
{/* 方式1绑定的函数 */}
);
}
}
三、阻止 DOM 事件:控制事件的传播与默认行为
事件阻止是交互逻辑中的常见需求,主要包括两种场景:阻止事件默认行为(如阻止表单提交刷新页面)和阻止事件冒泡(避免父元素事件被触发)。
1. 原生 JavaScript:通过事件对象方法阻止
原生 JS 中,通过事件处理函数的参数(event 对象)提供的方法实现阻止。
(1)阻止默认行为(event.preventDefault())
阻止元素的默认行为(如链接跳转、表单提交刷新页面、右键菜单等)。
点击不跳转
<script>
// 阻止链接跳转
const link = document.getElementById('link');
link.addEventListener('click', (e) => {
e.preventDefault(); // 关键:阻止默认跳转
console.log('链接被点击,但不跳转');
});
// 阻止表单提交刷新
const form = document.getElementById('form');
form.addEventListener('submit', (e) => {
e.preventDefault(); // 关键:阻止默认提交
console.log('表单提交,不刷新页面');
});
</script>
(2)阻止事件冒泡(event.stopPropagation())
事件冒泡是指事件从触发元素向上传播到父元素、祖先元素的过程。stopPropagation() 可阻止这一过程。
祖父元素
父元素
<script>
const grandparent = document.getElementById('grandparent');
const parent = document.getElementById('parent');
const child = document.getElementById('child');
// 祖父元素事件
grandparent.addEventListener('click', () => {
console.log('祖父元素事件触发');
});
// 父元素事件
parent.addEventListener('click', () => {
console.log('父元素事件触发');
});
// 子元素事件(阻止冒泡)
child.addEventListener('click', (e) => {
e.stopPropagation(); // 关键:阻止事件向上传播
console.log('子元素事件触发');
});
// 点击子元素,只输出 "子元素事件触发"(父和祖父不触发)
// 若注释掉 e.stopPropagation(),则依次输出:子 → 父 → 祖父
</script>
(3)阻止所有事件(event.stopImmediatePropagation())
不仅阻止冒泡,还会阻止当前元素上的其他同类型事件处理函数执行。
const btn = document.getElementById('btn');
// 第一个点击事件
btn.addEventListener('click', (e) => {
e.stopImmediatePropagation();
console.log('事件1');
});
// 第二个点击事件(不会执行)
btn.addEventListener('click', () => {
console.log('事件2'); // 不触发
});
// 点击按钮,只输出 "事件1"
2. Vue 框架:事件修饰符一键阻止
Vue 提供了 .prevent 和 .stop 修饰符,无需手动操作事件对象,直接在模板中声明即可。
(1)阻止默认行为(.prevent)
点击不跳转
<script setup>
const handleSubmit = () => {
console.log('表单提交成功');
};
const handleLinkClick = () => {
console.log('链接被点击');
};
</script>
(2)阻止事件冒泡(.stop)
祖父元素
父元素
<script setup>
const grandparentClick = () => {
console.log('祖父元素事件');
};
const parentClick = () => {
console.log('父元素事件');
};
const childClick = () => {
console.log('子元素事件'); // 点击子元素,只触发这个
};
</script>
(3)修饰符组合使用
修饰符可以链式组合,实现复杂需求:
点击不跳转且不冒泡
3. React 框架:通过合成事件方法阻止
React 中通过合成事件的 preventDefault() 和 stopPropagation() 方法阻止,用法与原生类似,但作用于合成事件系统。
(1)阻止默认行为(e.preventDefault())
function PreventDefaultDemo() {
const handleSubmit = (e) => {
e.preventDefault(); // 阻止表单默认提交
console.log('表单提交');
};
const handleLinkClick = (e) => {
e.preventDefault(); // 阻止链接跳转
console.log('链接被点击');
};
return (
);
}
(2)阻止事件冒泡(e.stopPropagation())
function StopPropagationDemo() {
const grandparentClick = () => {
console.log('祖父元素事件');
};
const parentClick = () => {
console.log('父元素事件');
};
const childClick = (e) => {
e.stopPropagation(); // 阻止冒泡
console.log('子元素事件');
};
return (
祖父元素
父元素
);
}
(3)React 与原生事件的冲突处理
若同时绑定了 React 事件和原生事件,需注意:
- React 事件在冒泡阶段触发,原生事件可能在捕获阶段触发,导致
stopPropagation()失效。 - 解决办法:使用
e.nativeEvent.stopImmediatePropagation()阻止所有事件传播。import { useRef, useEffect } from 'react'; function MixedEventDemo() { const btnRef = useRef(null); useEffect(() => { // 绑定原生事件 const btn = btnRef.current; btn.addEventListener('click', nativeHandleClick); return () => { btn.removeEventListener('click', nativeHandleClick); }; }, []); // 原生事件处理函数 const nativeHandleClick = () => { console.log('原生事件'); }; // React 事件处理函数 const reactHandleClick = (e) => { e.nativeEvent.stopImmediatePropagation(); // 阻止所有事件(包括原生) console.log('React 事件'); }; return ( ); } // 点击按钮,只输出 "React 事件"(原生事件被阻止) 总结:不同场景下的最佳实践
操作 原生 JavaScript Vue 框架 React 框架 获取 DOM 优先使用 querySelector/querySelectorAll(灵活),getElementById(高性能)通过 ref引用,在mounted/onMounted中访问通过 useRef(函数组件)或createRef(类组件),在useEffect/componentDidMount中访问绑定事件 优先使用 addEventListener(支持多函数绑定)使用 @事件名指令 + 事件修饰符(简洁高效)使用驼峰事件属性(如 onClick),函数组件推荐箭头函数传递参数阻止默认行为 event.preventDefault().prevent修饰符e.preventDefault()(合成事件)阻止冒泡 event.stopPropagation().stop修饰符e.stopPropagation()(合成事件)
核心原则:
- 原生开发:灵活使用
querySelector和addEventListener,注意事件冒泡和默认行为的控制。 - Vue 开发:充分利用
ref和事件修饰符,避免直接操作 DOM,遵循数据驱动思想。 - React 开发:通过
useRef安全获取 DOM,使用合成事件处理交互,注意this指向和事件传播机制。
掌握这些方法,你就能在不同前端场景下游刃有余地处理 DOM 操作,写出高效、可维护的代码。

浙公网安备 33010602011771号