4. DOM 事件高级 - 事件对象、事件委托、学生信息录入实例

31 阅读6分钟

1. 事件对象

事件对象 也是个对象,在这个对象里面有触发事件时的相关信息,例如鼠标点击事件中,事件对象就会记录鼠标点在哪个位置等信息。

事件绑定的回调函数的第一个参数就是事件对象,一般命名为 evente

image.png

image.png

事件对象部分常用 属性

image.png

图片随着鼠标移动 实例

  • 一张图片一直跟着鼠标移动
  • 鼠标在页面移动用 mousemove 事件
  • 不断把鼠标在页面中的坐标位置给图片

image.png

<!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>
      img {
        position: absolute;
        top: 0;
        left: 0;
        width: 60px;
        height: 60px;
      }
    </style>
  </head>

  <body>
    <img src="./code.png" alt="" />

    <script>
      let img = document.querySelector('img')
      img.addEventListener('mousemove', e => {
        // 位置值减图片大小的一半,让鼠标在图片中间
        img.style.left = e.clientX - 30 + 'px'
        img.style.top = e.clientY - 30 + 'px'
      })
    </script>
  </body>
</html>

按下回车键,可以发布消息:

  • 用到按下键盘事件,用 keydown 或 keyup 都可以,但弹起调用 send 点击事件时,使用 keydown 会有一个空格,因此建议使用 keyup
  • 如果用户按下的是回车键盘,则发布消息
  • 按键发布和点击发布按钮效果一样

image.png

2. 事件流

事件流 指的是事件完整执行过程中的流动路径

例如页面里面有一个 div 元素,当事件被触发时,就会经历两个阶段,即 捕获阶段冒泡阶段 ,简单来说,捕获阶段就是 从父到子 ,冒泡阶段则是 从子到父

image.png

2.1 事件冒泡

当一个元素的事件被触发时,同样的事件将会在该元素的所有的祖先元素中依次被触发,这一过程就是 事件冒泡

简单来说就是当一个元素触发事件后,会依次向上调用 所有父级 元素的同名事件,事件冒泡是默认存在的

例如:触发点击事件

image.png

<!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>
  </head>
  <style>
    .s {
      width: 100px;
      height: 50px;
      background-color: rgb(159, 251, 222);
    }
    .fa {
      width: 200px;
      height: 100px;
      background-color: rgb(245, 165, 216);
    }
  </style>
  <body>
    <div class="fa">
      <div class="s"></div>
    </div>
    <script>
      let fa = document.querySelector('.fa')
      let s = document.querySelector('.s')

      document.body.addEventListener('click',()=>{
        alert('body 的点击事件')
      })

      fa.addEventListener('click', () => {
        alert('父元素的点击事件')
      })

      s.addEventListener('click', () => {
        alert('触发了子元素的 click 事件')
      })
    </script>
  </body>
</html>

冒泡阶段:点击 “ s ” , 依次弹出 s ---> fa ---> body

image.png

2.2 事件捕获

从 DOM 的根元素开始去执行对应的事件(从外到里)称为 事件捕获

事件捕获需要写对应的代码才能看到效果:

image.png

监听事件传入的第三个参数 true ,表示 捕获阶段 触发,这一事件使用较少

如果传入 false 则代表是冒泡阶段,默认 就是 false

如果用 L0 事件监听,只有冒泡阶段,没有捕获阶段

例如:同一点击事件

<!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>
  </head>
  <style>
    .s {
      width: 100px;
      height: 50px;
      background-color: rgb(159, 251, 222);
    }
    .fa {
      width: 200px;
      height: 100px;
      background-color: rgb(245, 165, 216);
    }
  </style>
  <body>
    <div class="fa">
      <div class="s"></div>
    </div>
    <script>
      let fa = document.querySelector('.fa')
      let s = document.querySelector('.s')

      document.body.addEventListener('click',()=>{
        alert('body 的点击事件')
      },true)

      fa.addEventListener('click', () => {
        alert('父元素的点击事件')
      },true)

      s.addEventListener('click', () => {
        alert('触发了子元素的 click 事件')
      },true)
    </script>
  </body>
</html>

捕获阶段:点击 “ s ” , 依次弹出 body ---> fa ---> s

image.png

2.3 阻止事件流动

因为事件冒泡默认就存在,因此容易导致事件影响到父级元素,如果想把事件就限制在当前元素内,不向外冒泡,就需要阻止事件流动

阻止事件流动 需要拿到事件对象:

image.png

此方法可以阻断事件流动传播,不止在冒泡阶段有效,捕获阶段也有效

示例1:冒泡阶段阻止事件流动

image.png

示例2:捕获阶段阻止事件流动

image.png

鼠标经过事件推荐使用 mouseenter 和 mouseleave ,没有冒泡效果

2.4 阻止默认行为

阻止默认行为 preventDefault() ,例如表单域的跳转、点击连接不跳转等

  <body>
    <a href="www.baidu.com">百度</a>
    <script>
      let a = document.querySelector('a')

      a.addEventListener('click', e => {
        e.preventDefault()
      })
    </script>
  </body>

3. 事件委托

事件委托 是利用事件流的特征解决一些开发需求的知识技巧

  • 优点 :给父级元素添加事件,可以提高性能;
  • 原理 :事件委托其实是利用事件冒泡的特点;
  • 实现 :事件 对象.target 可以获得真正触发事件的元素
  <body>
    <div>123456</div>
    <script>
      let div = document.querySelector('div')

      div.addEventListener('click', e => {
        console.log(e.target)
        e.target.style.color = 'red'
      })
    </script>
  </body>

image.png

4. 实例:点击按钮,录入学生信息

image.png

删除时,使用事件委托来实现:

image.png

用 ID 来记录点击的按钮下标:

image.png

用事件委托来实现删除

image.png

完整代码:

* {
  margin: 0;
  padding: 0;
}

tbody button {
  width: 66px;
  height: 36px;
  font-size: 18px;
  background-color: rgb(37, 192, 244);
  border: none;
  border-radius: 3px;
  color: white;
}
h1 {
  text-align: center;
  color:#333;
  margin: 20px 0;
 
}
table {
  margin:0 auto;
  width: 800px;
  border-collapse: collapse;
  color:#004085;
}
th {
  padding: 10px;
  background: #cfe5ff;
  
  font-size: 20px;
  font-weight: 400;
}
td,th {
  border:1px solid #b8daff;
}
td {
  padding:10px;
  color:#666;
  text-align: center;
  font-size: 16px;
}
tbody tr {
  background: #fff;
}
tbody tr:hover {
  background: #e1ecf8;
}
.info {
  width: 900px;
  margin: 50px auto;
  text-align: center;
}
.info  input {
  width: 80px;
  height: 25px;
  outline: none;
  border-radius: 5px;
  border:1px solid #b8daff;
  padding-left: 5px;
}
.info button {
  width: 60px;
  height: 25px;
  background-color: #004085;
  outline: none;
  border: 0;
  color: #fff;
  cursor: pointer;
  border-radius: 5px;
}
.info .age {
  width: 50px;
}
<!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">
  <link rel="stylesheet" href="css/user.css">
  <title>添加学生信息</title>
</head>

<body>
  <h1>新增学生信息</h1>
  <div class="info">
    姓名:<input type="text" class="uname">
    年龄:<input type="text" class="age">
    性别: <select name="gender" id="" class="gender">
      <option value="男"></option>
      <option value="女"></option>
    </select>
    薪资:<input type="text" class="salary">
    就业城市:<select name="city" id="" class="city">
      <option value="北京">北京</option>
      <option value="上海">上海</option>
      <option value="广州">广州</option>
      <option value="深圳">深圳</option>
      <option value="陕西">陕西</option>
      </option>
    </select>
    <button class="add">录入</button>
  </div>

  <h1>就业榜</h1>
  <table>
    <thead>
      <tr>
        <th>学号</th>
        <th>姓名</th>
        <th>年龄</th>
        <th>性别</th>
        <th>薪资</th>
        <th>就业城市</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>
      <!-- <tr>
        <td>1001</td>
        <td>张三</td>
        <td>19</td>
        <td>男</td>

        <td>15000</td>
        <td>上海</td>
        <td>
          <button>删除(不允许删除)</button>
        </td>
      </tr> -->
    </tbody>
  </table>
  <script>
    //  1. 准备好数据后端的数据
    let arr = [
      { stuId: 1001, uname: '张三', age: 19, gender: '男', salary: '20000', city: '上海' },
      { stuId: 1002, uname: '李四', age: 29, gender: '男', salary: '30000', city: '北京' },
      { stuId: 1003, uname: '小明', age: 39, gender: '男', salary: '2000', city: '北京' },
    ]

    // 获取元素
    let tbody = document.querySelector('table tbody')
    let add = document.querySelector('.add')
    let uname = document.querySelector('.uname')
    let age = document.querySelector('.age')
    let gender = document.querySelector('.gender')
    let salary = document.querySelector('.salary')
    let city = document.querySelector('.city')

    // 初始化函数
    function ini() {
      // 先清空原来的数据,再新增
      tbody.innerHTML = ''

      for (let i = 0; i < arr.length; i++) {
        let tr = document.createElement('tr')
        tr.innerHTML = `
        <td>${arr[i].stuId}</td>
        <td>${arr[i].uname}</td>
        <td>${arr[i].age}</td>
        <td>${arr[i].gender}</td>

        <td>${arr[i].salary}</td>
        <td>${arr[i].city}</td>
        <td>
          <button id='${i}'>删除</button>
        </td>
      `
        tbody.appendChild(tr)

        // 复原表单的值
        uname.value = age.value = salary.value = ''
        gender.value = '男'
        city.value = '北京'
      }
    }

    ini() // 函数不调用不执行

    // 添加信息
    add.addEventListener('click', () => {
      // console.log(uname.value)
      if (!uname.value.trim()) {
        alert("学生姓名不允许为空,请重新添加")
        return
      }

      let stuId = arr[arr.length - 1].stuId + 1
      let arrNew = {
        stuId: stuId,
        uname: uname.value.trim(),
        age: age.value.trim(),
        gender: gender.value.trim(),
        salary: salary.value.trim(),
        city: city.value.trim()
      }
      arr.push(arrNew)
      ini()
    })

    // 事件委托 实现删除
    tbody.addEventListener('click', (e) => {
      if (arr.length === 1) {
        alert("必须保留一条数据")
        return
      }

      // console.dir(e.target)
      // 点击了删除按钮
      if (e.target.tagName === 'BUTTON') {
        // console.log(e.target)
        // console.log(e.target.id)  e.target.id = 数组下标
        arr.splice(e.target.id, 1)
        ini()
      }
    })
  </script>
</body>
</html>