Web APIs 知识总结【事件高级】

118 阅读8分钟

事件高级

事件对象

获取事件对象

是什么

也是个对象,这个对象里有事件触发时的相关信息

例如:鼠标点击事件中,事件对象就存了鼠标点在哪个位置等信息

如何获取

在事件绑定的回调函数的第一个参数就是事件对象

一般命名为 eventeve

tbody.addEventListener('click', function(e) {
      console.log(e)
});

事件对象常用属性

语法含义
e.type获取当前的事件类型
e.target触发事件的元素
e.clientX/e.clientY获取光标相对于浏览器可见窗口左上角的位置
e.offsetX/e.offsetY获取光标相对于当前 DOM 素左上角的位置
e.screenX/e.screenY获取光标相对于整个电脑屏幕左上角的位置
e.key用户按下的键盘键的值
e.stopPropagation()阻止冒泡
e.preventDefault()阻止默认事件,如不让链接跳转、不让表单提交

图例

事件.png



事件流

事件流与两个阶段说明

  1. 定义:事件流指的是事件完整执行过程中的流动路径

  2. 捕获阶段是从父到子

  3. 冒泡阶段是从子到父

事件捕获和事件冒泡

事件冒泡

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

  2. 事件冒泡是默认存在的。可以没有冒泡阶段。

事件捕获

  1. DOM 的根元素开始去执行对应的事件 (从外到里)

  2. 用户单击某个元素时,由 document 先相应,然后做两件事:

    1. 是不是在我的结构范围内触发的
    2. 我有没有子元素,有的话传下去,子元素重复做1、2步
  3. 若是用 onclick 事件监听,则只有冒泡阶段,没有捕获

  4. 捕获阶段一定需要!没有捕获阶段没有后续的事件。

注意

  1. js代码只能执行捕获或冒泡其中一个阶段
  2. addEventListener 第三个参数传入 true 代表是捕获阶段触发(很少使用)。若传入 false 代表冒泡阶段触发,默认就是 false
  3. 实际开发中很少使用事件捕获,更关注事件冒泡

图例

捕获冒泡.png

阻止事件流动

  1. 冒泡模式默认存在,容易导致事件影响到父级元素。想把事件就限制在当前元素内,就需要阻止事件流动

  2. 方法

    • 阻止冒泡语法

      事件对象.stopPropagation()
      
    • 鼠标经过事件

      • mouseovermouseout 会有冒泡效果

      • mouseentermouseleave 没有冒泡效果

    • 阻止默认行为

      e.preventDefault()
      
    • 两种注册事件的区别

      1. 传统 on 注册

        • 同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
        • 直接使用null覆盖偶就可以实现事件的解绑
        • 都是冒泡阶段执行的
      2. 事件监听注册

        • 语法:
          addEventListener(事件类型, 事件处理函数, 是否使用捕获)
          
        • 后面注册的事件不会覆盖前面注册的事件(同一个事件)
        • 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
        • 必须使用 removeEventListener (事件类型, 事件处理函数, 获取捕获或者冒泡阶段)
        • 匿名函数无法被解绑


事件委托

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

  2. 原理:利用事件冒泡的特点, 给父元素添加事件,子元素可以触发

  3. 优点:给父级元素加事件(可以提高性能)

  4. 语法:

    btn.addEventListeren('click' ,function(e){
            console.log(e.target)
    })
    

事件委托总结

事件委托

  1. 将事件绑定给已存在的父容器,让子元素进行触发
  2. 它的复用了事件委托的原理,当子元素触发事件之后,会将事件冒泡给父容器
  3. 如果想对真正触发事件的元素进行处理,可以使用 e.target获取当前真正触发事件的元素
  4. 如果子元素的操作不一样,需要通过判断,一般我们会对子元素添加一个标识,判断当前触发事件的元素是否有这个标识


总结

事件高级.png



习题思考

页面渲染

整体效果如下图所示。

图示.png

要求把数组的数据输出到页面上;点击录入按钮就把信息新增一行并录入相应的空格;点击删除按钮则删除相应的行。

由于本次案例是基于元素操作实现的,元素操作一定要体现到数据,用户操作是的界面元素,但是本质上是在操作数据。因此以前的 .valueappendChlid 已经不再适用。我们要基于数组内的数据进行操作。

数组的数据输出到页面上

  1. 拿到数据发现是一个数组,因此最开始是遍历数组,拿到每一个元素。

  2. 声明一个空字符串 htmlStr ,用模板字符串拼接结构。

  3. 输出到 tbody 上。

let htmlStr = ''
arr.forEach(function(ele, index) {
    // 拼接字符串
    htmlStr += `<tr>
    <td>${index+1}</td>
    <td>${ele.uname}</td>
    <td>${ele.age}</td>
    <td>${ele.gender}</td>
    <td>${ele.salary}</td>
    <td>${ele.city}</td>
    <td>
        <a href="javascript:" class="del" id="${ele.stuId}">删除</a>
    </td>
</tr>`;
});
// 把拼接好的字符串输出到tbody中
tbody.innerHTML = htmlStr

本小节犯错:

在最后一步把拼接好的字符串输出到 tbody 中时使用错了方法。

 tbody.appendChild(htmlStr)

结果报错。

Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.

原因是此时 htmlStr 是一个字符串,不是一个节点不能使用 appendChild 方法,而应该使用 innerHTML 方法输出。

添加数据

后期开发是要直接操作数据库内的数据,形成数据驱动视图,不能再用以前的创建 tr ,让 tbody 追加 tr 到末尾这种简单操作页面元素的方式,而是操作数组的形式。

  1. 获取发布按钮以及表单元素。点击后触发回调函数:

    1. 创建一个对象,因为数组内的元素都是对象,为该对象赋值,原对象有啥值就赋啥值。

    2. 数据学号为数组中最后一个元素的学号加1,没有的话赋个初始值;其余数据则用获取好的表单元素的值。

    3. push 方法把数据追加到数组中。

    4. 重新渲染一次页面,由于与第一步的代码一致,因此可以把前面的方法封装成一个函数,调用。PS:封装成函数后要先自调用一次,不然页面没有数据。

add.addEventListener('click', function() {
    // 数组内是对象的形式,因此我们声明一个对象,取值
    let obj = {
        // 学号为数组内最后一个对象的学号加1,如果数组为空则设置一个学号1001
        stuId: arr.length > 0 ? arr[arr.length - 1].stuId + 1 : 1001,
        uname: unameEle.value,
        age: ageEle.value,
        gender: genderEle.value,
        salary: salaryEle.value,
        city: cityEle.value
    }
    console.log(obj);
    arr.push(obj)
    init()
)

本小节细节

在对象获取值的地方,要在触发点击事件触发后再获取值,即把获取值的步骤写在点击事件回调函数内,而不是写在函数外。如果写在函数外,则会出现只获取到性别和城市的默认值。

之所以会这样,是因为写在回调函数外部的话,页面刚加载完毕就立刻获取完毕值,而此时用户还未输入值,待输入值按下录入按钮后,系统把一开始录入的初始值打印在页面,而没有默认值的部分自然是空值了。

删除数据

由于是面向数据库(本次是面向数组)操作,因此删除操作要把数据从数组内删除,数组删除操作需要用到代码 splice(索引 , 数量) ,再重新渲染一次页面。于是问题简单明了,在于如何获取到需要删除的值的索引。

以后业务处理的时候需要一个值, 你一般有两种方式获取到这个值。

  1. 传递参数
  2. 自己先存储再获取

此时能传递的参数只有索引,而索引值不能用于删除操作,删除操作应该用 id 这种具有唯一性的属性,看回数组内,符合条件的只有 stuId 这个对象属性,因此删除操作可以这么做:

  1. 在第一步渲染页面的拼接字符串步骤时为每个删除按钮添加一个 id 名,因为 id 名不允许重复,具有唯一性,其 id 值为自己的 stuId
  2. 由于删除按钮是未来事件源,因此用到事件委托,为父容器 tbody 绑定点击事件,设置事件对象,用 e.target 来判断触发点击事件的是哪个节点。再用一个变量,接收触发事件的节点的 stuId 值。
  3. 循环遍历数组内所有的数据,然后进行判断,如果有和被点击的事件对象 stuId 一样的,则取它的 i 值作为索引,删除,再重新渲染页面。
        tbody.addEventListener('click', function(e) {
            // 获取到当前触发事件的元素的id号
            let id = e.target.id
                // 遍历数组的数据,找到符合点击的数据id号
            for (let i = 0; i < arr.length; i++) {
                if (id == arr[i].stuId) {
                    // 删除,重新渲染数组
                    arr.splice(i, 1)
                    init()
                }
            }
        });

本小节难点

如何获取索引值是一个难点,如果没有现成的值传递,那就自己存储一个 id ,因为 id 是内置属性,方便后续操控事件对象 e 获取,其值为数组对象内具有唯一标识的 stuId ,再用事件对象 e.target.id 获取。用这个值作为识别是哪个元素节点的标识。

然后需要做的是判断,先遍历数组内所有的对象,找到其 stuId 属性与被点击的元素节点的 id 相同的那个对象,此时循环的变量 i 值就是很好的索引值,用于做 splice() 删除操作,最后再渲染页面,就大功告成。