MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript事件处理的实用技巧

2023-03-173.9k 阅读

JavaScript事件处理的基础概念

在JavaScript编程中,事件处理是一个至关重要的部分。它使得网页能够对用户的交互做出响应,比如点击按钮、滚动页面、输入文本等操作。事件是浏览器检测到的特定动作,而事件处理程序则是当事件发生时执行的JavaScript代码。

事件绑定的基本方式

  1. HTML属性方式 在HTML标签中直接添加事件属性来绑定事件处理程序。例如,为一个按钮添加点击事件:
<button onclick="alert('Hello, World!')">点击我</button>

这种方式简单直观,但存在一些缺点。首先,HTML和JavaScript代码混合在一起,不利于代码的维护和复用。其次,如果事件处理逻辑复杂,HTML标签会变得臃肿。 2. DOM对象属性方式 通过获取DOM元素,然后将事件处理函数赋值给其对应的事件属性。

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>DOM对象属性绑定事件</title>
</head>

<body>
  <button id="myButton">点击我</button>
  <script>
    const myButton = document.getElementById('myButton');
    myButton.onclick = function () {
      alert('通过DOM对象属性绑定的点击事件');
    };
  </script>
</body>

</html>

这种方式将JavaScript代码与HTML分离,提高了代码的可读性和维护性。但它也有局限性,对于同一个元素的同一个事件,只能绑定一个处理函数。如果多次赋值,后面的会覆盖前面的。 3. addEventListener方法 这是现代JavaScript中最常用的事件绑定方式。

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>addEventListener绑定事件</title>
</head>

<body>
  <button id="addEventButton">点击我</button>
  <script>
    const addEventButton = document.getElementById('addEventButton');
    addEventButton.addEventListener('click', function () {
      alert('通过addEventListener绑定的点击事件');
    });
  </script>
</body>

</html>

addEventListener方法可以为同一个元素的同一个事件绑定多个处理函数,并且支持捕获和冒泡两种事件流模型。其语法为element.addEventListener(eventType, callback, useCapture),其中eventType是事件类型(如'click''mouseover'等),callback是事件处理函数,useCapture是一个可选的布尔值,用于指定事件是在捕获阶段还是冒泡阶段触发,默认值为false(冒泡阶段)。

事件流与事件委托

事件流的概念

事件流描述了从页面中接收事件的顺序。在DOM中,存在两种事件流模型:捕获阶段和冒泡阶段。

  1. 捕获阶段 事件从最外层的祖先元素开始,向内层元素传播,直到到达目标元素。例如,当点击一个按钮时,事件会从document对象开始,依次经过htmlbody等祖先元素,最后到达按钮元素。
  2. 冒泡阶段 事件从目标元素开始,向外层的祖先元素传播,直到到达最外层的document对象。这是默认的事件传播方式。例如,还是点击按钮,事件会从按钮开始,依次经过bodyhtml,最后到达document
  3. 事件处理的阶段 一个完整的事件处理过程包括三个阶段:捕获阶段、目标阶段和冒泡阶段。在捕获阶段和冒泡阶段,事件会经过一系列的祖先元素,而在目标阶段,事件直接在目标元素上触发。

事件委托

事件委托是一种利用事件冒泡机制的设计模式。它将事件处理程序绑定到一个祖先元素上,而不是直接绑定到每个具体的目标元素上。当事件在目标元素上触发时,由于事件冒泡,它会传播到祖先元素,祖先元素的事件处理程序可以根据事件的目标来决定如何处理。 例如,假设有一个列表,每个列表项都需要添加点击事件:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>事件委托示例</title>
</head>

<body>
  <ul id="myList">
    <li>列表项1</li>
    <li>列表项2</li>
    <li>列表项3</li>
  </ul>
  <script>
    const myList = document.getElementById('myList');
    myList.addEventListener('click', function (event) {
      if (event.target.tagName === 'LI') {
        alert('你点击了:' + event.target.textContent);
      }
    });
  </script>
</body>

</html>

在这个例子中,点击任何一个列表项,事件都会冒泡到ul元素,ul元素的点击事件处理程序通过检查event.target来判断是哪个列表项被点击了。事件委托的优点有:

  • 减少内存占用:如果有大量的元素需要绑定事件,为每个元素单独绑定事件会占用大量的内存。而事件委托只需要在祖先元素上绑定一个事件处理程序。
  • 动态添加元素的支持:对于动态添加到页面的元素,不需要重新绑定事件。因为事件委托是基于祖先元素的,新添加的元素同样会触发祖先元素的事件。

常见的JavaScript事件类型

鼠标事件

  1. click 当用户点击元素时触发。可以是鼠标左键、右键或中键点击。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>click事件示例</title>
</head>

<body>
  <button id="clickButton">点击我触发click事件</button>
  <script>
    const clickButton = document.getElementById('clickButton');
    clickButton.addEventListener('click', function () {
      alert('click事件被触发');
    });
  </script>
</body>

</html>
  1. mouseovermouseout mouseover事件在鼠标指针进入元素时触发,mouseout事件在鼠标指针离开元素时触发。常用于实现悬停效果。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>mouseover和mouseout事件示例</title>
  <style>
    #hoverDiv {
      width: 200px;
      height: 100px;
      background-color: lightblue;
    }
  </style>
</head>

<body>
  <div id="hoverDiv"></div>
  <script>
    const hoverDiv = document.getElementById('hoverDiv');
    hoverDiv.addEventListener('mouseover', function () {
      this.style.backgroundColor = 'lightgreen';
    });
    hoverDiv.addEventListener('mouseout', function () {
      this.style.backgroundColor = 'lightblue';
    });
  </script>
</body>

</html>
  1. mousedownmouseup mousedown事件在鼠标按钮在元素上按下时触发,mouseup事件在鼠标按钮在元素上释放时触发。可以用于实现拖拽效果。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>mousedown和mouseup事件示例</title>
  <style>
    #dragDiv {
      width: 100px;
      height: 100px;
      background-color: orange;
      position: absolute;
    }
  </style>
</head>

<body>
  <div id="dragDiv"></div>
  <script>
    const dragDiv = document.getElementById('dragDiv');
    let isDragging = false;
    let startX, startY;
    dragDiv.addEventListener('mousedown', function (event) {
      isDragging = true;
      startX = event.clientX - this.offsetLeft;
      startY = event.clientY - this.offsetTop;
    });
    document.addEventListener('mousemove', function (event) {
      if (isDragging) {
        const newX = event.clientX - startX;
        const newY = event.clientY - startY;
        dragDiv.style.left = newX + 'px';
        dragDiv.style.top = newY + 'px';
      }
    });
    document.addEventListener('mouseup', function () {
      isDragging = false;
    });
  </script>
</body>

</html>

键盘事件

  1. keydown 当用户按下键盘上的任意键时触发,无论是否产生字符输入。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>keydown事件示例</title>
</head>

<body>
  <input type="text" id="keyInput">
  <script>
    const keyInput = document.getElementById('keyInput');
    keyInput.addEventListener('keydown', function (event) {
      console.log('按下的键码:' + event.keyCode);
    });
  </script>
</body>

</html>
  1. keypress 当用户按下一个能产生字符的键时触发。例如,按下字母键、数字键等。但按下功能键(如ShiftCtrl等)不会触发keypress事件。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>keypress事件示例</title>
</head>

<body>
  <input type="text" id="pressInput">
  <script>
    const pressInput = document.getElementById('pressInput');
    pressInput.addEventListener('keypress', function (event) {
      console.log('输入的字符:' + String.fromCharCode(event.charCode));
    });
  </script>
</body>

</html>
  1. keyup 当用户释放键盘上的任意键时触发。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>keyup事件示例</title>
</head>

<body>
  <input type="text" id="upInput">
  <script>
    const upInput = document.getElementById('upInput');
    upInput.addEventListener('keyup', function (event) {
      console.log('释放的键码:' + event.keyCode);
    });
  </script>
</body>

</html>

表单事件

  1. submit 当用户提交表单时触发。通常用于验证表单数据。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>submit事件示例</title>
</head>

<body>
  <form id="myForm">
    <input type="text" id="nameInput" required>
    <input type="submit" value="提交">
  </form>
  <script>
    const myForm = document.getElementById('myForm');
    myForm.addEventListener('submit', function (event) {
      const nameInput = document.getElementById('nameInput');
      if (nameInput.value === '') {
        alert('姓名不能为空');
        event.preventDefault();
      }
    });
  </script>
</body>

</html>
  1. input<input><textarea>等表单元素的值发生变化时触发。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>input事件示例</title>
</head>

<body>
  <input type="text" id="inputText">
  <div id="output"></div>
  <script>
    const inputText = document.getElementById('inputText');
    const outputDiv = document.getElementById('output');
    inputText.addEventListener('input', function () {
      outputDiv.textContent = '你输入的内容是:' + this.value;
    });
  </script>
</body>

</html>
  1. change<input><select><textarea>等表单元素的值发生改变并且失去焦点时触发。与input事件不同,change事件不是实时触发的。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>change事件示例</title>
</head>

<body>
  <select id="mySelect">
    <option value="option1">选项1</option>
    <option value="option2">选项2</option>
  </select>
  <div id="selectOutput"></div>
  <script>
    const mySelect = document.getElementById('mySelect');
    const selectOutput = document.getElementById('selectOutput');
    mySelect.addEventListener('change', function () {
      selectOutput.textContent = '你选择了:' + this.value;
    });
  </script>
</body>

</html>

事件对象的深入理解

事件对象的属性和方法

当事件发生时,事件处理函数会接收到一个事件对象作为参数。这个事件对象包含了与事件相关的各种信息。

  1. target target属性指向触发事件的目标元素。例如,在一个包含多个按钮的容器中,点击某个按钮,event.target就指向被点击的那个按钮。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>事件对象的target属性</title>
</head>

<body>
  <div id="buttonContainer">
    <button>按钮1</button>
    <button>按钮2</button>
  </div>
  <script>
    const buttonContainer = document.getElementById('buttonContainer');
    buttonContainer.addEventListener('click', function (event) {
      console.log('点击的目标元素是:' + event.target.textContent);
    });
  </script>
</body>

</html>
  1. currentTarget currentTarget属性指向绑定事件处理程序的元素。在事件委托的场景中,event.targetevent.currentTarget可能不同。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>事件对象的currentTarget属性</title>
</head>

<body>
  <ul id="listContainer">
    <li>列表项1</li>
    <li>列表项2</li>
  </ul>
  <script>
    const listContainer = document.getElementById('listContainer');
    listContainer.addEventListener('click', function (event) {
      console.log('绑定事件的元素是:' + event.currentTarget.tagName);
      console.log('触发事件的目标元素是:' + event.target.tagName);
    });
  </script>
</body>

</html>
  1. preventDefault preventDefault方法用于阻止事件的默认行为。例如,在点击链接时,默认行为是跳转到链接的href指定的页面。如果不想让链接跳转,可以调用event.preventDefault()
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>preventDefault方法示例</title>
</head>

<body>
  <a href="https://www.example.com" id="myLink">点击我</a>
  <script>
    const myLink = document.getElementById('myLink');
    myLink.addEventListener('click', function (event) {
      event.preventDefault();
      alert('链接的默认跳转行为被阻止');
    });
  </script>
</body>

</html>
  1. stopPropagation stopPropagation方法用于阻止事件的进一步传播。在事件冒泡或捕获过程中,调用这个方法可以使事件不再向上或向下传播。
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>stopPropagation方法示例</title>
</head>

<body>
  <div id="outerDiv">
    <div id="innerDiv"></div>
  </div>
  <script>
    const outerDiv = document.getElementById('outerDiv');
    const innerDiv = document.getElementById('innerDiv');
    outerDiv.addEventListener('click', function () {
      console.log('外层div被点击');
    });
    innerDiv.addEventListener('click', function (event) {
      console.log('内层div被点击');
      event.stopPropagation();
    });
  </script>
</body>

</html>

在这个例子中,点击内层div时,事件会触发内层div的点击事件处理程序,并且由于调用了stopPropagation,事件不会冒泡到外层div,所以不会触发外层div的点击事件处理程序。

高级事件处理技巧

防抖与节流

  1. 防抖(Debounce) 防抖是指在事件触发后,等待一定时间(如300毫秒),如果在这段时间内事件再次触发,则重新计时,直到最后一次触发事件后,等待计时结束才执行事件处理函数。常用于搜索框输入、窗口大小调整等场景,避免频繁触发事件导致性能问题。
function debounce(func, delay) {
  let timer;
  return function () {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

const input = document.getElementById('searchInput');
const debouncedFunction = debounce(function () {
  console.log('搜索内容:' + this.value);
}, 300);
input.addEventListener('input', debouncedFunction);

在这个例子中,当用户在搜索框中输入内容时,不会立即触发console.log,而是在用户停止输入300毫秒后才会触发。如果用户在300毫秒内继续输入,计时会重新开始。 2. 节流(Throttle) 节流是指在一定时间间隔内,无论事件触发多少次,都只执行一次事件处理函数。常用于滚动事件、鼠标移动事件等,控制事件处理函数的执行频率。

function throttle(func, interval) {
  let lastTime = 0;
  return function () {
    const context = this;
    const args = arguments;
    const now = new Date().getTime();
    if (now - lastTime >= interval) {
      func.apply(context, args);
      lastTime = now;
    }
  };
}

window.addEventListener('scroll', throttle(function () {
  console.log('窗口滚动中');
}, 300));

在这个例子中,无论窗口滚动多么频繁,console.log每300毫秒最多执行一次。

自定义事件

JavaScript允许开发者创建和触发自定义事件。这在组件化开发中非常有用,可以实现组件之间的解耦通信。

  1. 创建和触发自定义事件
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>自定义事件示例</title>
</head>

<body>
  <button id="customButton">触发自定义事件</button>
  <script>
    const customButton = document.getElementById('customButton');
    // 创建自定义事件
    const myCustomEvent = new CustomEvent('myCustomEvent', {
      detail: {
        message: '这是自定义事件携带的数据'
      }
    });
    // 监听自定义事件
    document.addEventListener('myCustomEvent', function (event) {
      console.log('自定义事件被触发,携带的数据:' + event.detail.message);
    });
    customButton.addEventListener('click', function () {
      // 触发自定义事件
      document.dispatchEvent(myCustomEvent);
    });
  </script>
</body>

</html>

在这个例子中,首先创建了一个名为myCustomEvent的自定义事件,并携带了一些数据。然后为document添加了对这个自定义事件的监听。当点击按钮时,触发自定义事件,监听器会接收到事件并处理携带的数据。

跨浏览器的事件处理

虽然现代浏览器对JavaScript事件处理的支持已经比较统一,但在一些旧版本的浏览器中,还是存在一些兼容性问题。例如,在IE8及以下版本中,没有addEventListener方法,而是使用attachEvent方法。为了实现跨浏览器的事件处理,可以封装一个兼容性函数:

function addEvent(element, eventType, callback) {
  if (element.addEventListener) {
    element.addEventListener(eventType, callback, false);
  } else if (element.attachEvent) {
    element.attachEvent('on' + eventType, function () {
      callback.call(element);
    });
  } else {
    element['on' + eventType] = callback;
  }
}

const myElement = document.getElementById('myElement');
addEvent(myElement, 'click', function () {
  alert('跨浏览器点击事件');
});

这个函数首先检查浏览器是否支持addEventListener,如果支持则使用它来绑定事件。如果不支持,则检查是否支持attachEvent,如果支持则使用它来绑定事件。如果两者都不支持,则使用DOM对象属性方式绑定事件。这样就可以在不同的浏览器中统一地进行事件处理。