按头细抠 Vue 之事件处理

我是卡喵妹,你可以叫我卡妹,你也可以叫我喵妹,或者你叫我小菜喵也可以。(杀疯了杀疯了)

前言

我第一次看 Vue 的官方文档已经是几年前了,第一次真的是认真研读了一次,后来在学习工作中遇到忘记的地方会翻到对应的地方扫几眼,再也没有完整且认真地研读过了。

最近不忘初心重学 Vue,越看越有味道越看越上头停不下来了,还是发现了几个有点意思的地方,搜索搜集了感兴趣的内容在此记录?。

一、DOM 事件流

想必看过《红宝书》的前端 er 对标题都不会特别陌生,我们来看看定义:当一个 HTML 元素产生一个事件时,该事件会在树形结构的 DOM 上面沿着元素节点路径进行传播,事件所经过的路径结点都会收到该事件,这个传播过程可称为 DOM 事件流

img02.jpeg

二、DOM 事件流模型

DOM 事件流分为 捕获型事件流冒泡型事件流。两种事件流分别对应三阶段 DOM 事件流模型中的捕获阶段和冒泡阶段:

  1. 捕获阶段:事件从最外面的祖先节点依次传递到最里面的后代节点
  2. 目标阶段:真正的目标节点正在处理事件的阶段
  3. 冒泡阶段:事件又从最里面的后代节点逐层传出到最外面的祖先节点

img01.jpeg

三、Vue 之事件处理

Vue 中提供了事件绑定的语法糖,我们可以很简单地在标签中直接使用 @click="handleClick($event)" 就可以绑定点击事件。

而且在 Vue 里面,Vue 的事件触发默认为冒泡过程监听,意思就是上面 @click="handleClick($event)" 中点击事件的执行是冒泡过程触发的。

四、Vue 之事件修饰符

4.1 .capture

捕获监听器

<div @click="log(1)" @click.capture="log(1)" style="background-color: #00f">
  <div @click="log(2)" @click.capture="log(2)" style="background-color: #66f">
    <div @click="log(3)" @click.capture="log(3)" style="background-color: #ccf">
      <div @click="log(4)" @click.capture="log(4)" style="background-color: #fff">
        点击这里
      </div>
    </div>
  </div>
</div>
复制代码

控制台打印结果为:1 2 3 4 4 3 2 1

截屏2021-07-06 下午11.31.01.png

符合 DOM 事件流模型,先捕获后冒泡,.capture 的作用就是在捕获阶段触发事件。

4.2 .stop

阻止事件传递

<div @click="log(1)" @click.capture="log(1)" style="background-color: #00f">
  <div @click="log(2)" @click.capture="log(2)" style="background-color: #66f">
    <div @click="log(3)" @click.capture.stop="log(3)" style="background-color: #ccf">
      <div @click="log(4)" @click.capture="log(4)" style="background-color: #fff">
        点击这里
      </div>
    </div>
  </div>
</div>
复制代码

控制台打印结果为:1 2 3

说明 .stop 真正的作用是阻止事件的传递,不仅阻止捕获事件流,也会阻止冒泡事件流。

.stop 最常见的应用场景:比如移动端购物车的商品列表,点击商品列表跳转商品详情,商品列表右下角有个删除商品的按钮,点击按钮删除该商品。假设我们正常监听点击事件,当我们点击删除按钮时,触发删除事件,但是由于事件冒泡,稍后触发跳转商品详情的事件。结论就是:在删除按钮监听事件增加 .stop 事件修饰符,避免事件传递造成非预期的结果。

4.3 .prevent

阻止默认事件的触发:比如某些 HTML 标签拥有自身的默认事件,如 a[href=""]button[type="submit"] 标签在冒泡结束后会开始执行默认事件注意默认事件虽然是冒泡后开始,但不会因为 .stop 事件修饰符阻止事件传递而停止。

<div @click="log(1)" @click.capture="log(1)" style="background-color: #00f">
  <div @click="log(2)" @click.capture="log(2)" style="background-color: #66f">
    <div @click="log(3)" @click.capture.stop="log(3)" style="background-color: #ccf">
      <a @click="log(4)" @click.capture="log(4)" href="javascript: console.log('x')" 
style="background-color: #fff">
        点击这里
      </a>
    </div>
  </div>
</div>
复制代码

控制台打印结果为:1 2 3 x

上例中说明了 .stop 事件修饰符无法阻止 a[href=""] 中默认事件的触发。

<!-- 将 .prevent 绑在冒泡阶段 -->
<div @click="log(1)" @click.capture="log(1)" style="background-color: #00f">
  <div @click="log(2)" @click.capture="log(2)" style="background-color: #66f">
    <div @click="log(3)" @click.capture="log(3)" style="background-color: #ccf">
      <a @click.prevent="log(4)" @click.capture="log(4)" href="javascript: console.log('x')" 
style="background-color: #fff">
        点击这里
      </a>
    </div>
  </div>
</div>

<!-- 将 .prevent 绑在捕获阶段 -->
<div @click="log(1)" @click.capture="log(1)" style="background-color: #00f">
  <div @click="log(2)" @click.capture="log(2)" style="background-color: #66f">
    <div @click="log(3)" @click.capture="log(3)" style="background-color: #ccf">
      <a @click="log(4)" @click.capture.prevent="log(4)" href="javascript: console.log('x')" 
style="background-color: #fff">
        点击这里
      </a>
    </div>
  </div>
</div>
复制代码

控制台打印结果为:1 2 3 4 4 3 2 1

上述两例证明,无论是在冒泡阶段阻止默认事件还是在捕获阶段阻止默认事件,结果都是一样的。

4.4 .passive

不阻止默认事件的触发:浏览器只有等内核线程执行到事件监听器对应的 JavaScript 代码时,才能知道内部是否会调用 preventDefault 函数来阻止事件的默认行为,所以浏览器本身是没有办法对这种场景进行优化的。这种场景下,用户的手势事件无法快速产生,会导致页面无法快速执行滑动逻辑,从而让用户感觉到页面卡顿。(通俗点说就是每次事件产生,浏览器都会去查询一下是否有 preventDefault 阻止该次事件的默认动作。我们加上 passive 就是为了告诉浏览器,不用查询了,我们没用 preventDefault 阻止默认动作。

应用场景:一般用在滚动监听 @scoll@touchmove,因为滚动监听过程中移动每个像素都会产生一次事件,每次都使用内核线程查询 prevent 会使滑动卡顿。我们通过 passive 将内核线程查询跳过,可以大大提升滑动的流畅度。

注:passive 和 prevent 冲突,不能同时绑定在一个监听器上。

4.5 .self

只有点击元素本身的时候才会触发这个元素的事件。

<div @click="log(1)" @click.capture="log(1)" style="background-color: #00f">
  <div @click="log(2)" @click.capture.self="log(2)" style="background-color: #66f">
    <div @click.self="log(3)" @click.capture="log(3)" style="background-color: #ccf">
      <a @click="log(4)" @click.capture="log(4)" href="javascript: console.log('x')" 
style="background-color: #fff">
        点击这里
      </a>
    </div>
  </div>
</div>
复制代码
  • 点击 a 标签控制台打印结果为:1 3 4 4 2 1 x
  • 点击 3 图层控制台打印结果为:1 3 3 2 1
<div @click="log(1)" @click.capture="log(1)" style="background-color: #00f">
  <div @click="log(2)" @click.capture="log(2)" style="background-color: #66f">
    <div @click="log(3)" @click.capture="log(3)" style="background-color: #ccf">
      <a @click.prevent.self="log(4)" @click.capturet="log(4)" href="javascript: console.log('x')" 
style="background-color: #fff">
        点击这里
        <div style="background-color: #ccc">5</div>
      </a>
    </div>
  </div>
</div>
复制代码
  • 点击 a 标签控制台打印结果为:1 2 3 4 4 3 2 1
  • 点击 5 图层控制台打印结果为:1 3 4 3 2 1
<div @click="log(1)" @click.capture="log(1)" style="background-color: #00f">
  <div @click="log(2)" @click.capture="log(2)" style="background-color: #66f">
    <div @click="log(3)" @click.capture="log(3)" style="background-color: #ccf">
      <a @click.self.prevent="log(4)" @click.capture="log(4)" href="javascript: console.log('x')" 
style="background-color: #fff">
        点击这里
        <div style="background-color: #ccc">5</div>
      </a>
    </div>
  </div>
</div>
复制代码
  • 点击 a 标签控制台打印结果为:1 2 3 4 4 3 2 1
  • 点击 5 图层控制台打印结果为:1 2 3 4 3 2 1 x(x 在最后是由于默认事件在冒泡结束之后执行)

注:self 写在 prevent 前时,prevent 会被 self 影响。直接点击这个目标时才会触发 prevent。因为 self 拦截住了监听,后面的 prevent 也一起失效了。

4.6 .native

原型绑定:只有使用 vue 组件才会用到该修饰符。

<el-input @click.native=""> 相当于把事件绑定在 input[class="el-input__inner"] 上。

4.7 .once

使得元素的事件只触发一次。

绑定 .once 的监听器只会触发一次,在第一次触发后该监听器会被移除。

参考优秀文章

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享