10分钟了解vue修饰符
2024-07-02 11:40:03

本来没想整理的,基本上是用到的时候现查为主。

然后刷到一个面试题要求随便举几个例子,虽然磕磕绊绊的说出来了一些,但是这实在不符合一个老前端应有的知识储备。

索性这里统一整理下,反正也不是太麻烦的事情。

正文

事件修饰符Vue框架本身提供的事件语法糖,无论是V2还是V3,大部分都是通用的。

这里就不详细去分析他们的原理,仅收集供面试和平时开发时使用。

事件修饰符

在正式进入事件修饰符事件之前,我们需要先简单了解一些概念,不然新人直接看这些修饰符可能会懵。

事件相关内容委托

众所周知,我们平时写的页面是由DOM树不断嵌套堆叠而成的,当我们与DOM交互的时候,实际上是要穿透这一层层DOM结构,触发到对应节点的事件。

早年这里会考个面试题,如果每层节点都绑定事件,那么这是这每层事件的执行顺序是怎样的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const count = ref(0)
return { count }
},
methods: {
test(num){
console.log(num)
}
},
template: `
<div @click="test(1)">
<div @click="test(2)">
<div @click="test(3)">
测试
</div>
</div>
</div>`
}).mount('#app')
</script>
// 输出结果:
// 3
// 2
// 1

看到这个结果,我们可能会疑惑,为什么最外层的绑定的事件反而最后才触发?

由此,我们需要明白两个基础概念:事件冒泡事件委托

事件冒泡

事件冒泡(dubbed bubbling):当一个元素接收到事件的时候,会把他接收到的事件传给自己的父级,一直到 window (注意这里传递的仅仅是事件,例如click、focus等等这些事件, 并不传递所绑定的事件函数。)

事件源 =>根节点(由内到外)进行事件传播。

所以我们会发现,这次的事件触发结果,实际上就是由内而外的执行。

事件捕获

事件捕获(event capturing): 当鼠标点击或者触发dom事件时(被触发dom事件的这个元素被叫作事件源),浏览器会从根节点 =>事件源(由外到内)进行事件传播。

事件捕获与事件冒泡是比较类似的,最大的不同在于事件传播的方向。

这里我们不再用vue讨巧举例了,因为vue的事件本质上就是封装了事件注册方法:addEventListener('click',() =>{}, false)

事件注册:addEventListener,通过控制最后一个传值,我们就能决定触发的方向是由内向外还是由外向内。

我们将三个div,由外到内命名为:big,center,small,再看一结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
big.addEventListener('click',()=>{
console.log('big----事件捕获')
},true)
center.addEventListener('click',()=>{
console.log('center----事件捕获')
},true)
small.addEventListener('click',()=>{
console.log('small----事件捕获')
},true)

big.addEventListener('click',()=>{
console.log('big----事件冒泡')
},false)
center.addEventListener('click',()=>{
console.log('center----事件冒泡'
}, false)
small.addEventListener('click',()=>{
console.log('small----事件冒泡')
}, false)

// 输出结果:
// big----事件捕获
// center----事件捕获
// small----事件捕获

// small----事件冒泡
// center----事件冒泡
// big----事件冒泡

这里我们就会发现,两者的触发方向完全不一样。

  • 事件冒泡:由内向外。
  • 事件捕获:由外向内。
阻止事件传播

当点击页面的表格内的按钮时候,有时候我们会触发行点击事件,这时候我们希望仅仅点击按钮,那么我们必然要阻止这种事件传播的问题。

阻止事件传播,常用的有两种方法:

  • event.stopPropagation() 阻止事件传播,不会阻止同一元素上的其他的事件处理程。
  • event.stopImmediatePropagation() 阻止事件传播,阻止同一元素上的其他的事件处理程。

当然也不仅仅局限于此,有时候,表单被提交时默认的submit事件,我们不希望触发,这里我们也可以通过这种方式阻止这些默认事件。

事件委托

事件委托也称为事件代理

就是利用事件冒泡,把子元素的事件都绑定到父元素上。

如果子元素阻止了事件冒泡,那么委托就无法实现。

1
不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。

例子

现在要为每一个li添加一个事件,假设li有100个,于是就需要为每一个li添加一个事件,这样会占用100个内存。

因此,如果使用事件委托的话,可以利用事件的冒泡机制,为ul绑定一个事件,那么点击任意一个li的时候,都会将事件触发到父元素ul上。

1
2
3
4
5
6
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
...
</ul>
代码展示
1
2
3
4
5
6
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
...
</ul>

可以使用event.target获取到点击的元素,event.target.innerHTML获取到点击的元素的内容。 如下代码,当你点击li时,会添加一个红色的背景,再次点击,会将背景变为白色。

1
2
3
4
5
6
7
8
9
10
11
const ulE = document.querySelector('#ul')

ulE.addEventListener('click', function (event) {
console.log('ulE event', event.target, event.target.innerHTML)
const target = event.target
if (target.style.backgroundColor !== 'red') {
target.style.backgroundColor = 'red'
} else {
target.style.backgroundColor = '#fff'
}
})

简单的来说,就是事件的目标源委托给父级元素,减少性能的损耗。

故此,使用事件代理/委托可以提高性能,减少注册的事件。

v-on事件修饰符

vue官方提供了很多处理事件的修饰符,主要我们平时很多用不到,所以并未深入了解。

我这里简单的按照使用频率,将事件修饰符分为通用修饰符和特殊修饰符,特殊修饰符我会单独讲一下它们的应用场景。

通用修饰符

这些修饰符有些在vue2中,Vue3就没有了相应的修饰符,所以这里会备注一下支持的版本。

修饰符名称 支持版本 事件 备注
.native v2 监听组件根元素的原生事件
.prevent v2,v3 阻止事件的默认动作 比如表单默认的submit按钮会刷新页面,我们用这个就可以阻止刷新页面
.once v2,v3 仅触发一次 可用来避免用户重复点击导致多次提交表单
.stop v2,v3 阻止事件传播/冒泡 避免事件冒泡触发了父元素的方法
.self v2,v3 只当事件是从侦听器绑定的元素本身触发时才触发回调。 通过事件委托的形式,减少页面性能小号
.passive v2,v3 滚动事件的默认行为 (scrolling) 将立即发生而非等待 onScroll 完成 移动端常用处理滚动监听,PC端很少用
.capture v2,v3 指向内部元素的事件,在被内部元素处理前,先被外部处理 采用了捕获的方式,可以调整执行顺序

使用修饰符时需要注意调用顺序,因为相关代码是以相同的顺序生成的。

因此使用 @click.prevent.self 会阻止元素及其子元素的所有点击事件的默认行为

@click.self.prevent 则只会阻止对元素本身的点击事件的默认行为。

.passive修饰符

.passive 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能,详情参考CSDN的这篇:Vue事件处理:.passive修饰符与应用场景

请勿同时使用 .passive.prevent,因为 .passive 已经向浏览器表明了你不想阻止事件的默认行为。

如果你这么做了,则 .prevent 会被忽略,并且浏览器会抛出警告。

键盘事件

.{keycode | keyAlias}

键盘中每个按键都有自己对应的keycode,vue提供的语法糖,让我们可以直接使用对应事件绑定,也可以用对应的keycode绑定。

这里,我们以常用的回车事件为例子,我们按键加入enter修饰符,代表我们点击回车时,可以触发test()事件。

1
2
3
<el-input @keypress.enter="test()">
<el-input @keypress.13="test()">
<!-- enter事件的keycode对应13,所以这两个例子都是回车触发按钮。 -->

这里我们就可以发现,使用keycode和修饰符都可以达到对应的效果。

vue官方提供了很多便利性的事件,如果您需要将方法绑定对应的按钮,不妨参考MDN的KeyCode码表,这样绑定会方便很多。

修饰符名称 事件
.enter 回车
.delete Delete或Backspace
.ctrl ctrl按键
.alt alt按键
.shift shift按键
.tab tab按键
.esc esc按键
.space space空格按键
.up,.down,.left,.right 上下左右方向键
.meta win键/mac的commond键
[.exact]( <button @click.ctrl=”onClick”>A <button @click.ctrl.exact=”onCtrlClick”>A <button @click.exact=”onClick”>A) 修饰符允许精确控制触发事件所需的系统修饰符的组合。

注意:如果多个组件注册了键盘事件,最好销毁,保证键盘事件的唯一性,不然有时候会导致整个页面出现一些意料之外的问题。

另外,有人可能对exact的描述有些迷惑,这里放一下官方的例子,一看就明白,就是为了精确操作使用的。

1
2
3
4
5
6
7
8
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>

鼠标

@click鼠标事件,没什么好说的。

修饰符名称 事件
.left 左键
.right 右键
.middle 中间

属性修饰符

v-bind

v-bind的修饰符我们几乎很少用,这里推荐大家看看,知道有这么回事儿就行。

修饰符名称 备注
.prop 强制绑定为 DOM property
.attr 强制绑定为 DOM attribute
.camel 将短横线命名的 attribute 转变为驼峰式命名
.sync vue2支持,vue3不支持了。

这里直接放一下官方的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />

<!-- 动态 attribute 名 -->
<button v-bind:[key]="value"></button>

<!-- 缩写 -->
<img :src="imageSrc" />

<!-- 缩写形式的动态 attribute 名 (3.4+),扩展为 :src="src" -->
<img :src />

<!-- 动态 attribute 名的缩写 -->
<button :[key]="value"></button>

<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />

<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]"></div>

<!-- style 绑定 -->
<div :style="{ fontSize: size + 'px' }"></div>
<div :style="[styleObjectA, styleObjectB]"></div>

<!-- 绑定对象形式的 attribute -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

<!-- prop 绑定。“prop” 必须在子组件中已声明。 -->
<MyComponent :prop="someThing" />

<!-- 传递子父组件共有的 prop -->
<MyComponent v-bind="$props" />

<!-- XLink -->
<svg><a :xlink:special="foo"></a></svg>

v-model

修饰符名称 备注
.trim 去除输入框首尾字符串
.number 将用户输入的类型由string型转为number型
.lazy 使得用户在输入数据之后,当数据失去焦点或点击回车时,才会进行数据的更新

结语

事件修饰符平时用的不多,最多就是输入框判空,表单单次提交,键盘回车等常用事件的使用,平时没有别的用法。

关于事件冒泡和事件捕获,这个就更是远古面试题了,如今在梳理修饰符时候刷到,属实有点老乡见老乡了。

如今仔细梳理了一番,感觉自己确实受益匪浅。

虽然依然用的场景可能不太多,但是相对以前,可能在开发中会更得心应手一些?

参考

Vue3-事件处理

浅谈js的事件机制,事件冒泡、事件捕获、事件代理(事件委托)

JS中的事件冒泡、事件捕获、事件委托

MDN_什么是事件