Skip to content

1.事件

事件是编程语言中的术语,它是用来描述程序的行为或状态的,一旦行为或状态发生改变,便立即调用一个函数。
例如:用户使用【鼠标点击】网页中的一个按钮、用户使用【鼠标拖拽】网页中的一张图片

1.1 事件监听

什么是事件监听?
就是让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函数做出响应,也称为绑定事件或者注册事件
比如鼠标经过显示下拉菜单,比如点击可以播放轮播图等等
结合 DOM 使用事件时,需要为 DOM 对象添加事件监听,等待事件发生(触发)时,便立即调用一个函数。
addEventListener` 是 DOM 对象专门用来添加事件监听的方法,它的两个参数分别为【事件类型】和【事件回调】。

语法:

javascript
element.addEventListener(event, function, useCapture);

参数说明:

  • event - 事件名称(如 'click', 'mouseover' 等,不带 on 前缀)
  • function - 事件触发时执行的函数(事件处理函数)
  • useCapture - 可选,布尔值,指定事件是在捕获阶段还是冒泡阶段触发(默认 false)
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>事件监听</title>
</head>
<body>
  <h3>事件监听</h3>
  <p id="text">为 DOM 元素添加事件监听,等待事件发生,便立即执行一个函数。</p>
  <button id="btn">点击改变文字颜色</button>
  <script>
    // 1. 获取 button 对应的 DOM 对象
    const btn = document.querySelector('#btn')

    // 2. 添加事件监听
    btn.addEventListener('click', function () {
      console.log('等待事件被触发...')
      // 改变 p 标签的文字颜色
      let text = document.getElementById('text')
      text.style.color = 'red'
    })

    // 3. 只要用户点击了按钮,事件便触发了!!!
  </script>
</body>
</html>

完成事件监听分成3个步骤:

  1. 获取 DOM 元素
  2. 通过 addEventListener 方法为 DOM 节点添加事件监听
  3. 等待事件触发,如用户点击了某个按钮时便会触发 click 事件类型
  4. 事件触发后,相对应的回调函数会被执行

1.2 事件类型

click 译成中文是【点击】的意思,它的含义是监听(等着)用户鼠标的单击操作,除了【单击】还有【双击】dblclick

html
<script>
  // 双击事件类型
  btn.addEventListener('dblclick', function () {
    console.log('等待事件被触发...');
    // 改变 p 标签的文字颜色
    const text = document.querySelector('.text')
    text.style.color = 'red'
  })

  // 只要用户双击击了按钮,事件便触发了!!!
</script>

结论:【事件类型】决定了事件被触发的方式,如 click 代表鼠标单击,dblclick 代表鼠标双击。

1.3 事件处理程序

addEventListener 的第2个参数是函数,这个函数会在事件被触发时立即被调用,在这个函数中可以编写任意逻辑的代码,如改变 DOM 文本颜色、文本内容等。

html
<script>
  // 双击事件类型
  btn.addEventListener('dblclick', function () {
    console.log('等待事件被触发...')
    
    const text = document.querySelector('.text')
    // 改变 p 标签的文字颜色
    text.style.color = 'red'
    // 改变 p 标签的文本内容
    text.style.fontSize = '20px'
  })
</script>

2.事件类型

2.1 鼠标事件

鼠标事件是用户使用鼠标操作时触发的事件:

javascript
click        // 鼠标单击(最常用)
dblclick     // 鼠标双击
mousedown    // 鼠标按下(左键/中键/右键按下时触发)
mouseup      // 鼠标释放(左键/中键/右键释放时触发)
mouseover    // 鼠标移入(移入元素或其子元素时触发,会冒泡)
mouseout     // 鼠标移出(移出元素或其子元素时触发,会冒泡)
mousemove    // 鼠标移动(在元素上移动时持续触发)
mouseenter   // 鼠标进入(移入元素时触发,不会冒泡)
mouseleave   // 鼠标离开(移出元素时触发,不会冒泡)

2.2 键盘事件

键盘事件是用户操作键盘时触发的事件:

javascript
keydown      // 键盘按下(任意键按下时触发,包括功能键)
keyup        // 键盘释放(任意键释放时触发)
keypress     // 键盘按键(仅对产生字符的键触发,已废弃,推荐使用 keydown/keyup)

2.3 表单事件

表单事件是与表单元素相关的事件:

javascript
submit       // 表单提交(form 元素提交时触发)
change       // 值改变(input、select、textarea 的值改变并失去焦点时触发)
input        // 输入事件(input、textarea 的值实时改变时触发)
focus        // 获取焦点(元素获得焦点时触发)
blur         // 失去焦点(元素失去焦点时触发)

2.4 文档/窗口事件

文档和窗口相关的事件:

javascript
load         // 页面加载完成(所有资源加载完成时触发)
DOMContentLoaded // DOM结构加载完成(DOM 树构建完成时触发)
resize       // 窗口大小改变(窗口尺寸变化时触发)
scroll       // 滚动(元素或窗口滚动时触发)

2.5 事件类型使用示例

鼠标事件示例

html
<button id="btn">点击我</button>
<div id="box" style="width: 200px; height: 200px; background: lightblue;"></div>

<script>
const btn = document.getElementById('btn');
const box = document.getElementById('box');

// 单击事件
btn.addEventListener('click', function() {
    console.log('按钮被单击');
});

// 双击事件
btn.addEventListener('dblclick', function() {
    console.log('按钮被双击');
});

// 鼠标移入和移出
box.addEventListener('mouseenter', function() {
    console.log('鼠标进入盒子');
    this.style.background = 'lightgreen';
});

box.addEventListener('mouseleave', function() {
    console.log('鼠标离开盒子');
    this.style.background = 'lightblue';
});
</script>
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>轮播图点击切换</title>
  <style>
    * {
      box-sizing: border-box;
    }

    .slider {
      width: 560px;
      height: 400px;
      overflow: hidden;
    }

    .slider-wrapper {
      width: 100%;
      height: 320px;
    }

    .slider-wrapper img {
      width: 100%;
      height: 100%;
      display: block;
    }

    .slider-footer {
      height: 80px;
      background-color: rgb(100, 67, 68);
      padding: 12px 12px 0 12px;
      position: relative;
    }

    .slider-footer .toggle {
      position: absolute;
      right: 0;
      top: 12px;
      display: flex;
    }

    .slider-footer .toggle button {
      margin-right: 12px;
      width: 28px;
      height: 28px;
      appearance: none;
      border: none;
      background: rgba(255, 255, 255, 0.1);
      color: #fff;
      border-radius: 4px;
      cursor: pointer;
    }

    .slider-footer .toggle button:hover {
      background: rgba(255, 255, 255, 0.2);
    }

    .slider-footer p {
      margin: 0;
      color: #fff;
      font-size: 18px;
      margin-bottom: 10px;
    }

    .slider-indicator {
      margin: 0;
      padding: 0;
      list-style: none;
      display: flex;
      align-items: center;
    }

    .slider-indicator li {
      width: 8px;
      height: 8px;
      margin: 4px;
      border-radius: 50%;
      background: #fff;
      opacity: 0.4;
      cursor: pointer;
    }

    .slider-indicator li.active {
      width: 12px;
      height: 12px;
      opacity: 1;
    }
  </style>
</head>

<body>
  <div class="slider">
    <div class="slider-wrapper">
      <img src="./images/slider01.jpg" alt="" />
    </div>
    <div class="slider-footer">
      <p>对人类来说会不会太超前了?</p>
      <ul class="slider-indicator">
        <li class="active"></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
      </ul>
      <div class="toggle">
        <button class="prev">&lt;</button>
        <button class="next">&gt;</button>
      </div>
    </div>
  </div>
  <script>
    // 1. 初始数据
    const data = [
      { url: './images/slider01.jpg', title: '对人类来说会不会太超前了?', color: 'rgb(100, 67, 68)' },
      { url: './images/slider02.jpg', title: '开启剑与雪的黑暗传说!', color: 'rgb(43, 35, 26)' },
      { url: './images/slider03.jpg', title: '真正的jo厨出现了!', color: 'rgb(36, 31, 33)' },
      { url: './images/slider04.jpg', title: '李玉刚:让世界通过B站看到东方大国文化', color: 'rgb(139, 98, 66)' },
      { url: './images/slider05.jpg', title: '快来分享你的寒假日常吧~', color: 'rgb(67, 90, 92)' },
      { url: './images/slider06.jpg', title: '哔哩哔哩小年YEAH', color: 'rgb(166, 131, 143)' },
      { url: './images/slider07.jpg', title: '一站式解决你的电脑配置问题!!!', color: 'rgb(53, 29, 25)' },
      { url: './images/slider08.jpg', title: '谁不想和小猫咪贴贴呢!', color: 'rgb(99, 72, 114)' },
    ]
    // 获取元素
    const img = document.querySelector('.slider-wrapper img')
    const p = document.querySelector('.slider-footer p')
    const footer = document.querySelector('.slider-footer')
    // 1. 右按钮业务
    // 1.1 获取右侧按钮 
    const next = document.querySelector('.next')
    let i = 0  // 信号量 控制播放图片张数
    // 1.2 注册点击事件

    next.addEventListener('click', function () {
      // console.log(11)
      i++
      // 1.6判断条件  如果大于8 就复原为 0
      // if (i >= 8) {
      //   i = 0
      // }
      i = i >= data.length ? 0 : i
      // 1.3 得到对应的对象
      // console.log(data[i])
      // 调用函数
      toggle()
    })

    // 2. 左侧按钮业务
    // 2.1 获取左侧按钮 
    const prev = document.querySelector('.prev')
    // 1.2 注册点击事件
    prev.addEventListener('click', function () {
      i--
      // 判断条件  如果小于0  则爬到最后一张图片索引号是 7
      // if (i < 0) {
      //   i = 7
      // }
      i = i < 0 ? data.length - 1 : i
      // 1.3 得到对应的对象
      // console.log(data[i])
      // 调用函数
      toggle()
    })

    // 声明一个渲染的函数作为复用
    function toggle() {
      // 1.4 渲染对应的数据
      img.src = data[i].url
      p.innerHTML = data[i].title
      footer.style.backgroundColor = data[i].color
      // 1.5 更换小圆点    先移除原来的类名, 当前li再添加 这个 类名
      document.querySelector('.slider-indicator .active').classList.remove('active')
      document.querySelector(`.slider-indicator li:nth-child(${i + 1})`).classList.add('active')
    }



    // 3. 自动播放模块
    let timerId = setInterval(function () {
      // 利用js自动调用点击事件  click()  一定加小括号调用函数
      next.click()
    }, 1000)


    // 4. 鼠标经过大盒子,停止定时器
    const slider = document.querySelector('.slider')
    // 注册事件
    slider.addEventListener('mouseenter', function () {
      // 停止定时器
      clearInterval(timerId)
    })

    // 5. 鼠标离开大盒子,开启定时器
    // 注册事件
    slider.addEventListener('mouseleave', function () {
      // 停止定时器
      if (timerId) clearInterval(timerId)
      // 开启定时器
      timerId = setInterval(function () {
        // 利用js自动调用点击事件  click()  一定加小括号调用函数
        next.click()
      }, 1000)
    })
  </script>
</body>

</html>

键盘事件示例

html
<input type="text" id="input" placeholder="输入内容">

<script>
const input = document.getElementById('input');

input.addEventListener('keydown', function(event) {
    console.log('键盘按下:', event.key);
});

input.addEventListener('keyup', function(event) {
    console.log('键盘释放:', event.key);
});
</script>
html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        ul {

            list-style: none;
        }

        .mi {
            position: relative;
            width: 223px;
            margin: 100px auto;
        }

        .mi input {
            width: 223px;
            height: 48px;
            padding: 0 10px;
            font-size: 14px;
            line-height: 48px;
            border: 1px solid #e0e0e0;
            outline: none;
        }

        .mi .search {
            border: 1px solid #ff6700;
        }

        .result-list {
            display: none;
            position: absolute;
            left: 0;
            top: 48px;
            width: 223px;
            border: 1px solid #ff6700;
            border-top: 0;
            background: #fff;
        }

        .result-list a {
            display: block;
            padding: 6px 15px;
            font-size: 12px;
            color: #424242;
            text-decoration: none;
        }

        .result-list a:hover {
            background-color: #eee;
        }
    </style>

</head>

<body>
    <div class="mi">
        <input type="search" placeholder="小米笔记本">
        <ul class="result-list">
            <li><a href="#">全部商品</a></li>
            <li><a href="#">小米11</a></li>
            <li><a href="#">小米10S</a></li>
            <li><a href="#">小米笔记本</a></li>
            <li><a href="#">小米手机</a></li>
            <li><a href="#">黑鲨4</a></li>
            <li><a href="#">空调</a></li>
        </ul>
    </div>
    <script>
        // 1. 获取元素
        const input = document.querySelector('[type=search]')
        const ul = document.querySelector('.result-list')
        // console.log(input)
        // 2. 监听事件 获得焦点
        input.addEventListener('focus', function () {
            // ul显示
            ul.style.display = 'block'
            // 添加一个带有颜色边框的类名
            input.classList.add('search')
        })
        // 3. 监听事件 失去焦点
        input.addEventListener('blur', function () {
            ul.style.display = 'none'
            input.classList.remove('search')
        })
    </script>
</body>

</html>

表单事件示例

html
<form id="form">
    <input type="text" id="username" placeholder="用户名">
    <input type="password" id="password" placeholder="密码">
    <button type="submit">提交</button>
</form>

<script>
const form = document.getElementById('form');
const username = document.getElementById('username');

// 表单提交
form.addEventListener('submit', function(event) {
    event.preventDefault(); // 阻止表单默认提交行为
    console.log('表单提交');
});

// 输入框实时输入
username.addEventListener('input', function(event) {
    console.log('输入内容:', event.target.value);
});

// 输入框获取焦点
username.addEventListener('focus', function() {
    console.log('获取焦点');
    this.style.borderColor = 'blue';
});

// 输入框失去焦点
username.addEventListener('blur', function() {
    console.log('失去焦点');
    this.style.borderColor = '#ccc';
});
</script>
html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>评论回车发布</title>
  <style>
    .wrapper {
      min-width: 400px;
      max-width: 800px;
      display: flex;
      justify-content: flex-end;
    }

    .avatar {
      width: 48px;
      height: 48px;
      border-radius: 50%;
      overflow: hidden;
      background: url(./images/avatar.jpg) no-repeat center / cover;
      margin-right: 20px;
    }

    .wrapper textarea {
      outline: none;
      border-color: transparent;
      resize: none;
      background: #f5f5f5;
      border-radius: 4px;
      flex: 1;
      padding: 10px;
      transition: all 0.5s;
      height: 30px;
    }

    .wrapper textarea:focus {
      border-color: #e4e4e4;
      background: #fff;
      height: 50px;
    }

    .wrapper button {
      background: #00aeec;
      color: #fff;
      border: none;
      border-radius: 4px;
      margin-left: 10px;
      width: 70px;
      cursor: pointer;
    }

    .wrapper .total {
      margin-right: 80px;
      color: #999;
      margin-top: 5px;
      opacity: 0;
      transition: all 0.5s;
    }

    .list {
      min-width: 400px;
      max-width: 800px;
      display: flex;
    }

    .list .item {
      width: 100%;
      display: flex;
    }

    .list .item .info {
      flex: 1;
      border-bottom: 1px dashed #e4e4e4;
      padding-bottom: 10px;
    }

    .list .item p {
      margin: 0;
    }

    .list .item .name {
      color: #FB7299;
      font-size: 14px;
      font-weight: bold;
    }

    .list .item .text {
      color: #333;
      padding: 10px 0;
    }

    .list .item .time {
      color: #999;
      font-size: 12px;
    }
  </style>
</head>

<body>
  <div class="wrapper">
    <i class="avatar"></i>
    <textarea id="tx" placeholder="发一条友善的评论" rows="2" maxlength="200"></textarea>
    <button>发布</button>
  </div>
  <div class="wrapper">
    <span class="total">0/200字</span>
  </div>
  <div class="list">
    <div class="item" style="display: none;">
      <i class="avatar"></i>
      <div class="info">
        <p class="name">清风徐来</p>
        <p class="text">大家都辛苦啦,感谢各位大大的努力,能圆满完成真是太好了[笑哭][支持]</p>
        <p class="time">2022-10-10 20:29:21</p>
      </div>
    </div>
  </div>
  <script>
    const tx = document.querySelector('#tx')
    const total = document.querySelector('.total')
    const item = document.querySelector('.item')
    const text = document.querySelector('.text')
    // 1. 当我们文本域获得了焦点,就让 total 显示出来
    tx.addEventListener('focus', function () {
      total.style.opacity = 1
    })
    // 2. 当我们文本域失去了焦点,就让 total 隐藏出来
    tx.addEventListener('blur', function () {
      total.style.opacity = 0
    })
    // 3. 检测用户输入
    tx.addEventListener('input', function () {
      // console.log(tx.value.length)  得到输入的长度
      total.innerHTML = `${tx.value.length}/200字`
    })

    // 4. 按下回车发布评论
    tx.addEventListener('keyup', function (e) {
      // 只有按下的是回车键,才会触发
      // console.log(e.key)
      if (e.key === 'Enter') {
        // 如果用户输入的不为空就显示和打印
        if (tx.value.trim()) {
          // console.log(11)
          item.style.display = 'block'
          // console.log(tx.value)  // 用户输入的内容
          text.innerHTML = tx.value

        }
        // 等我们按下回车,结束,清空文本域
        tx.value = ''
        // 按下回车之后,就要把 字符统计 复原
        total.innerHTML = '0/200字'
      }

    })
  </script>
</body>

</html>

窗口事件示例

javascript
// 页面加载完成
window.addEventListener('load', function() {
    console.log('页面加载完成');
});

// DOM 结构加载完成
document.addEventListener('DOMContentLoaded', function() {
    console.log('DOM 结构加载完成');
});

// 窗口大小改变
window.addEventListener('resize', function() {
    console.log('窗口宽度:', window.innerWidth);
    console.log('窗口高度:', window.innerHeight);
});

// 滚动事件
window.addEventListener('scroll', function() {
    console.log('滚动位置:', window.scrollY);
});

3. 事件对象

3.1 什么是事件对象

事件对象(Event Object)是事件触发时自动传递给事件处理函数的对象,它包含了与该事件相关的所有信息。

3.2 获取事件对象

事件对象作为事件处理函数的参数自动传入:

javascript
element.addEventListener('click', function(event) {
    // event 就是事件对象
    console.log(event);
});

3.3 常用事件对象属性

鼠标事件属性

javascript
event.type          // 事件类型(如 'click')
event.target        // 触发事件的元素(实际被点击的元素)
event.currentTarget // 绑定事件的元素
event.clientX       // 鼠标相对于视口的 X 坐标
event.clientY       // 鼠标相对于视口的 Y 坐标
event.pageX         // 鼠标相对于文档的 X 坐标
event.pageY         // 鼠标相对于文档的 Y 坐标
event.offsetX       // 鼠标相对于目标元素的 X 坐标
event.offsetY       // 鼠标相对于目标元素的 Y 坐标
event.button        // 鼠标按键(0=左键, 1=中键, 2=右键)

键盘事件属性

javascript
event.key           // 按键名称(如 'Enter', 'a', 'ArrowUp')
event.code          // 按键代码(如 'Enter', 'KeyA', 'ArrowUp')
event.keyCode       // 按键码(已废弃,不推荐使用)
event.ctrlKey       // 是否按下了 Ctrl 键
event.shiftKey      // 是否按下了 Shift 键
event.altKey        // 是否按下了 Alt 键
event.metaKey       // 是否按下了 Meta 键(Windows 下是 Win 键)

通用属性

javascript
event.timeStamp     // 事件发生的时间戳
event.bubbles       // 事件是否冒泡
event.cancelable    // 事件是否可以取消默认行为

3.4 常用事件对象方法

javascript
event.preventDefault()   // 阻止事件的默认行为
event.stopPropagation()  // 阻止事件冒泡
event.stopImmediatePropagation() // 阻止事件冒泡并阻止同一元素的其他监听器

3.5 事件对象使用示例

鼠标事件对象

html
<div id="box" style="width: 300px; height: 200px; background: lightblue; padding: 20px;">
    <button id="btn">点击我</button>
</div>

<script>
const box = document.getElementById('box');
const btn = document.getElementById('btn');

btn.addEventListener('click', function(event) {
    console.log('事件类型:', event.type);
    console.log('目标元素:', event.target);
    console.log('当前元素:', event.currentTarget);
    console.log('视口坐标:', event.clientX, event.clientY);
    console.log('文档坐标:', event.pageX, event.pageY);
    console.log('相对坐标:', event.offsetX, event.offsetY);
});

box.addEventListener('click', function(event) {
    console.log('box 被点击,事件源:', event.target.tagName);
});
</script>

键盘事件对象

html
<input type="text" id="input" placeholder="按任意键">

<script>
const input = document.getElementById('input');

input.addEventListener('keydown', function(event) {
    console.log('按键名称:', event.key);
    console.log('按键代码:', event.code);
    console.log('Ctrl键:', event.ctrlKey);
    console.log('Shift键:', event.shiftKey);
    console.log('Alt键:', event.altKey);
    
    // 检测快捷键 Ctrl + Enter
    if (event.ctrlKey && event.key === 'Enter') {
        console.log('按下了 Ctrl + Enter');
    }
});
</script>

阻止默认行为

html
<a href="https://www.example.com" id="link">点击跳转</a>
<form id="form">
    <input type="text" placeholder="输入内容">
    <button type="submit">提交</button>
</form>

<script>
const link = document.getElementById('link');
const form = document.getElementById('form');

// 阻止链接跳转
link.addEventListener('click', function(event) {
    event.preventDefault();
    console.log('链接跳转被阻止');
});

// 阻止表单提交
form.addEventListener('submit', function(event) {
    event.preventDefault();
    console.log('表单提交被阻止');
});
</script>

阻止事件冒泡

html
<div id="outer" style="width: 200px; height: 200px; background: lightblue; padding: 20px;">
    <div id="inner" style="width: 100px; height: 100px; background: lightgreen;"></div>
</div>

<script>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');

outer.addEventListener('click', function() {
    console.log('外层元素被点击');
});

inner.addEventListener('click', function(event) {
    console.log('内层元素被点击');
    event.stopPropagation(); // 阻止事件冒泡到外层元素
});
</script>

4. 环境对象

4.1 什么是环境对象

环境对象指的是函数内部特殊的变量 this,它代表当前函数运行时所处的环境对象。

4.2 this 的指向规则

在普通函数中,this 的指向取决于函数的调用方式:

4.2.1 直接调用函数

javascript
function fn() {
    console.log(this); // window(严格模式下为 undefined)
}
fn();

4.2.2 对象方法调用

javascript
const obj = {
    name: '张三',
    sayHello: function() {
        console.log(this); // obj 对象
        console.log('你好,我是', this.name);
    }
};
obj.sayHello(); // 调用对象方法

4.2.3 DOM 事件监听中的 this

重点: 在事件监听函数中,this 指向绑定事件的 DOM 元素!

html
<button id="btn">点击我</button>

<script>
const btn = document.getElementById('btn');

btn.addEventListener('click', function() {
    console.log(this); // <button> 元素
    console.log(this.id); // btn
    this.style.background = 'red'; // 修改按钮背景色
});
</script>

4.3 环境对象使用示例

示例1:使用 this 获取绑定事件的元素

html
<button class="btn">按钮1</button>
<button class="btn">按钮2</button>
<button class="btn">按钮3</button>

<script>
const buttons = document.querySelectorAll('.btn');

buttons.forEach(function(btn) {
    btn.addEventListener('click', function() {
        console.log('点击了按钮:', this.textContent);
        this.style.background = 'lightblue';
    });
});
</script>

示例2:使用 this 操作元素

html
<div class="box" style="width: 100px; height: 100px; background: lightblue;">盒子1</div>
<div class="box" style="width: 100px; height: 100px; background: lightblue;">盒子2</div>
<div class="box" style="width: 100px; height: 100px; background: lightblue;">盒子3</div>

<script>
const boxes = document.querySelectorAll('.box');

boxes.forEach(function(box) {
    box.addEventListener('click', function() {
        // 使用 this 操作当前被点击的元素
        this.style.background = 'red';
        this.style.color = 'white';
        this.textContent = '被点击了';
    });
});
</script>

示例3:使用 this 实现选项卡切换

html
<div id="tab">
    <button class="active">选项1</button>
    <button>选项2</button>
    <button>选项3</button>
    <div class="content" style="display: block;">内容1</div>
    <div class="content">内容2</div>
    <div class="content">内容3</div>
</div>

<script>
const buttons = document.querySelectorAll('#tab button');
const contents = document.querySelectorAll('#tab .content');

buttons.forEach(function(btn, index) {
    btn.addEventListener('click', function() {
        // 移除所有按钮的 active 类
        buttons.forEach(function(b) {
            b.classList.remove('active');
        });
        
        // 为当前点击的按钮添加 active 类
        this.classList.add('active');
        
        // 隐藏所有内容
        contents.forEach(function(content) {
            content.style.display = 'none';
        });
        
        // 显示对应索引的内容
        contents[index].style.display = 'block';
    });
});
</script>

4.4 箭头函数中的 this

注意: 箭头函数没有自己的 this,它会继承外层作用域的 this

html
<button id="btn">点击我</button>

<script>
const btn = document.getElementById('btn');

// 箭头函数中的 this 不指向按钮元素
btn.addEventListener('click', () => {
    console.log(this); // window
    // this.style.background = 'red'; // 报错!
});

// 如果需要使用 this,应该使用普通函数
btn.addEventListener('click', function() {
    console.log(this); // <button> 元素
    this.style.background = 'red';
});
</script>

5. 回调函数

5.1 什么是回调函数

回调函数(Callback Function)是作为参数传递给另一个函数,并在该函数内部被调用的函数。

简单理解: 如果一个函数 A 被作为参数传递给函数 B,并且在函数 B 内部被调用,那么函数 A 就是回调函数。

5.2 回调函数的基本使用

示例1:最简单的回调函数

javascript
// 定义一个接收回调函数的函数
function greet(callback) {
    console.log('开始打招呼');
    callback(); // 调用回调函数
    console.log('结束打招呼');
}

// 定义回调函数
function sayHello() {
    console.log('你好!');
}

// 调用函数,传入回调函数
greet(sayHello);

// 输出:
// 开始打招呼
// 你好!
// 结束打招呼

示例2:使用匿名函数作为回调

javascript
greet(function() {
    console.log('大家好!');
});

示例3:使用箭头函数作为回调

javascript
greet(() => {
    console.log('箭头函数回调');
});

5.3 回调函数传参

回调函数可以接收参数:

javascript
// 定义带参数的回调函数
function calculate(a, b, callback) {
    console.log('计算中...');
    const result = a + b;
    callback(result); // 将计算结果传递给回调函数
}

// 定义接收参数的回调函数
function showResult(result) {
    console.log('计算结果:', result);
}

calculate(10, 20, showResult);

// 输出:
// 计算中...
// 计算结果: 30

5.4 事件监听中的回调函数

在事件监听中,事件处理函数就是回调函数:

javascript
const btn = document.getElementById('btn');

// 这个匿名函数就是回调函数
btn.addEventListener('click', function() {
    console.log('按钮被点击了!');
});

// 命名函数作为回调函数
function handleClick() {
    console.log('按钮被点击了!');
}
btn.addEventListener('click', handleClick);

5.5 回调函数的应用场景

场景1:数组方法中的回调函数

javascript
const numbers = [1, 2, 3, 4, 5];

// forEach 中的回调函数
numbers.forEach(function(num) {
    console.log(num);
});

// map 中的回调函数
const doubled = numbers.map(function(num) {
    return num * 2;
});
console.log(doubled);

// filter 中的回调函数
const evenNumbers = numbers.filter(function(num) {
    return num % 2 === 0;
});
console.log(evenNumbers);

场景2:定时器中的回调函数

javascript
// setTimeout 中的回调函数
setTimeout(function() {
    console.log('2秒后执行');
}, 2000);

// setInterval 中的回调函数
let count = 0;
const timer = setInterval(function() {
    count++;
    console.log('每秒执行一次', count);
    
    if (count >= 5) {
        clearInterval(timer); // 清除定时器
    }
}, 1000);

场景3:使用回调函数处理点击事件

html
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button id="btn3">按钮3</button>

<script>
const btn1 = document.getElementById('btn1');
const btn2 = document.getElementById('btn2');
const btn3 = document.getElementById('btn3');

// 为每个按钮添加点击事件
btn1.addEventListener('click', function() {
    console.log('按钮1被点击');
});

btn2.addEventListener('click, function() {
    console.log('按钮2被点击');
});

btn3.addEventListener('click', function() {
    console.log('按钮3被点击');
});
</script>

场景4:封装一个通用的事件处理函数

html
<button class="btn" data-msg="消息1">按钮1</button>
<button class="btn" data-msg="消息2">按钮2</button>
<button class="btn" data-msg="消息3">按钮3</button>

<script>
const buttons = document.querySelectorAll('.btn');

// 定义一个通用的点击处理函数
function handleClick(event) {
    const msg = this.getAttribute('data-msg');
    console.log(msg);
    this.style.background = 'lightblue';
}

// 为所有按钮绑定同一个回调函数
buttons.forEach(function(btn) {
    btn.addEventListener('click', handleClick);
});
</script>

5.6 回调函数的优点

  1. 代码复用: 同一个回调函数可以被多次调用
  2. 代码分离: 将函数的定义和调用分离
  3. 异步处理: 适合处理异步操作(如定时器、网络请求)
  4. 灵活性: 可以根据需要传入不同的回调函数

5.7 回调地狱

当多个异步操作需要按顺序执行时,会产生回调嵌套,形成回调地狱:

javascript
// 回调地狱示例(不推荐)
setTimeout(function() {
    console.log('第一步');
    setTimeout(function() {
        console.log('第二步');
        setTimeout(function() {
            console.log('第三步');
            // 继续嵌套...
        }, 1000);
    }, 1000);
}, 1000);

解决回调地狱的方法:

  • 使用 Promise
  • 使用 async/await
javascript
// 使用 Promise 和 async/await 解决
function delay(ms) {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

async function run() {
    await delay(1000);
    console.log('第一步');
    await delay(1000);
    console.log('第二步');
    await delay(1000);
    console.log('第三步');
}

run();

5.8 总结

  • 回调函数是作为参数传递给另一个函数的函数
  • 在事件监听中,事件处理函数就是回调函数
  • 回调函数可以接收参数,提高灵活性
  • 合理使用回调函数可以简化代码,提高复用性
  • 避免过度嵌套产生回调地狱,可以使用 Promise 或 async/await 解决

Released under the MIT License.