前端 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 元素):

<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 通过「驼峰命名」的事件属性(如 onClickonChange)绑定事件,事件处理函数接收 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 事件"(原生事件被阻止)

  • 总结:不同场景下的最佳实践

    操作原生 JavaScriptVue 框架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 操作,写出高效、可维护的代码。

posted @ 2025-12-05 09:56  yangykaifa  阅读(3)  评论(0)    收藏  举报