问题:1,vue的capture修饰符是如何实现的?2,为什么要使用passive,vue的passive修饰符的功能是如何实现的?
目录
事件绑定的三种方式
事件修饰符
1,stop
2,多个修饰符串连
3,只阻止默认行为
4,capture
5,self
6,once
7,passvie
js事件机制的三个阶段
源码
在vue模板中的组件上绑定事件执行代码,有三种方式:
1,将代码直接内嵌写在v-on指令表达式中,例如:
<!-- 直接写在v-on指令表达式中 -->
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
2,绑定到一个事件方法上:
<!-- 绑定到事件方法 -->
<button v-on:click="greet">Greet</button>
name: "DealWithEvent",
data: () => ({
name: "xx",
}),
greet: function(event) {
console.log(this.name, event.target.tagName);
},
在事件方法中,默认第一个参数是特殊变量$event,不管在模板中有没有通过greet($event)
显式传递。
在控制台中,打印的this.name并不是“DealWithEvent”,而是“xx”。事件方法的作用域是当前组件,this指向当前的组件实例vm。这是因为在vue源码中,new Function(code..)
在执行时绑定的作用域就是当前的组件的作用域。
tagName是html元素的属性,是html5的特征,并不是vue设置的。
3,还有一种方式,在指令表达式中调用事件方法:
<!-- 在v-on指令表达式中调用方法 -->
<button v-on:click="greet2({'name':123,'target':{'tagName':'button'}},$event)">Greet2</button>
这种方式的好处,就是可以主动使用特殊变量$event,还可以传递其它参数给事件方法。
那么三种方式如何使用呢?
如果是简单的代码,直接写在表达式中;如果代码较多,扩展出一个事件方法,写在mehtods中;如果默认的事件绑定方式不能满足需求,再用第三种方法。
为简便开发,vue为事件绑定以声明的方式提供了一些修饰符。这些修饰符实现的功能,以代码同样也可以实现,但直接写在模板里,更简洁方便。
在列表中阻止事件向上冒泡
<!-- 事件不再冒泡,停止传播 -->
<a
v-on:click.prevent="doThat"
style=".."
>
<a
v-on:click.stop="doThis"
style=".."
>阻止事件的默认行为</a>
</a>
使用prevent是阻止事件的默认行为,使用prevent相当于调用event.preventDefault()。
使用stop是阻止事件进一步派发,相当于调用event.stopPropagation()。
这个示例的运行效果是,当单击内部的链接a时,只执行一个doThis函数;而如何将stop修饰符去了,doThat也会派发。
事件修饰符可以串连并用,例如:
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat">prevent & stop</a>
一般两个修饰符都可以并用,只有passive与prevent两个是矛盾的,不能并用。
只使用修饰符,不监听事件,例如:
<!-- 只有修饰符 -->
<form v-on:submit.prevent>prevent只有修饰符,没有事件监听</form>
这种情况下,只是阻止表单中submit按钮单击时的默认提交行为,但是并不监听任何事件。
在表单上使用该修饰符,相当于在该表单上的所有事件,默认都调用event.preventDefault。
监听捕捉阶段的事件,例如:
<div v-on:click.capture.stop="doThis">
capture捕获阶段..
<br />
<a
v-on:click.prevent="doThat"
style=".."
>阻止事件的默认行为</a>
</div>
运行效果:
在这个示例中,当单击发生在内部的灰色区域上时,如果加了stop,只响应外部的监听;只有去掉stop,单击内部才有两个响应。
capture.stop
同时使用的作用是,在捕捉阶段就监听事件,并且阻止事件进一步派发,也就是说,事件还没进门,就已经被门卫挂在门外了。
vue的capture修饰符是如何实现的?
capture修饰符的实现,是通过js的基本API完成的。在js的事件监听上,存在这样一种形式:
target.addEventListener(type, listener, useCapture);
第三个参数useCapture默认为false,只有为true时,才会在捕捉阶段触发事件函数的执行。
附useCapture的参数说明:
useCapture 可选
Boolean,在DOM树中,注册了listener的元素, 是否要先于它下面的EventTarget,调用该listener。 当useCapture(设为true) 时,沿着DOM树向上冒泡的事件,不会触发listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可以查看 事件流 及 JavaScript Event order 文档。如果没有指定, useCapture 默认为 false 。
从说明可以看出,当使用useCapture
为true
时,在从目标节点向上的冒泡阶段中,便不会再触发这个listener
的执行。这也很理解,因为它在捕捉阶段已经被执行过了。但如果在同一个节点上,或相邻的其它节点上,使用useCapture=false
注册了事件监听,其事件函数仍然是会执行的。
只在目标阶段监听事件,例如:
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<div v-on:click.self="doThat">self</div>
在这个示例中,只有单击发生在这个div上时,不是发生在包含它的父级上,是正好发生在它的身上,事件派发函数执行。
以js方式实现同样的效果,需要启用捕捉阶段的监听,并判断当前的事件对象是不是这个div,远不如加一个self修饰符简单。
只监听一次,例如:
<!-- 只监听一次 -->
<a v-on:click.once="doThis">once</a>
这个最简单,监听事件执行函数,执行完了就把事件监听移除了。在vue的事件机制中,vm.$once具有同样的功能。
使用passive修饰符,提高渲染效率
<!-- 使用passive及时渲染,改进渲染效果 -->
<div v-on:scroll.passive="onScroll" style="width:300px;height:300px;overflow-y: scroll;">
<div v-for="n in 100" :key="n" style="width:100%;height:20px;background: gray;">{{n}}</div>
</div>
运行效果:
在这个示例中,使用了passive修饰符,不等函数体执行完,div的默认滚动行为就已经发生了。如果函数体的代码执行起来开销大,使用这个特性可以显著改善UE体验。
passive要求使用组件的默认滚动行为,所以与阻止默认行为的prevent修饰符就不能同时使用。prevent 是拦截默认事件,passive是不拦截默认事件。
那么使用passive的意义在哪里?
浏览器只有等内核线程执行到事件函数的代码时,才能知道函数内部是否会调用了preventDefault函数来阻止事件的默认行为,所以浏览器本身是没有办法对这种场景进行优化的。在这种场景下,如果涉及到用户交互的事件无法快速产生,会导致页面无法及时渲染而让用户感到页面卡顿。
现在加上passive就是为了告诉浏览器,不用每次查询了,我们没用preventDefault阻止默认动作。
对于一些频繁触发的交互事件,例如scroll、touchmove、mouseover等,都可以使用passive提高浏览器的工作效率。
vue的passive修饰符的功能是如何实现的?
vue是基于js本身的API实现的。事件监听函数有一个形式是这样的:
target.addEventListener(type, listener, options);
其实第三个参数options,可以包括一个passive属性:
passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
js是一门基于ECMAScript标准的语言,与ActionScript3是同源语言。js的事件机制与as3一样,具有三个阶段:
窗体Document先是监听到事件,然后一级一级向内部的子组件派发,直到找到目标节点,这是第一阶段:捕捉。
找到了目标节点,即鼠标或触控点点中的元素,这是第二阶段:目录。
从第二阶段向上走,一路冒泡派发,这是最后一个阶段:冒泡。
平时开发默认监听的事件,都不包括捕捉阶段。因为捕捉阶段的事件在开启监听时,需要显式将addEventListener
的参数capture
设置为true
。
组件在DOM
树中是分层的,有父组件,有子组件。在每一层中派发的事件,称为代。方法event.stopPropagation()
阻止的是事件向下一代派发;而方法event.stopImmediatePropagation()
阻止的是同一代中其它事件函数的执行。
https://git.code.tencent.com/shiqiaomarong/vue-go-rapiddev-example/tags/v20200120
涉及本文的源码主要在:
vue-and-go-example/simple-vue-project/src/DealWithEvent.vue