.stop
: 调用event.stopPropagation()
,即阻止事件冒泡。.prevent
: 调用event.preventDefault()
,即阻止默认事件。.capture
: 添加事件侦听器时使用capture
模式,即使用事件捕获模式处理事件。.self
: 只当事件是从侦听器绑定的元素本身触发时才触发回调。.{keyCode | keyAlias}
: 只当事件是从特定键触发时才触发回调。.native
: 监听组件根元素的原生事件,即注册组件根元素的原生事件而不是组件自定义事件的。.once
: 只触发一次回调。.left(2.2.0)
: 只当点击鼠标左键时触发。.right(2.2.0)
: 只当点击鼠标右键时触发。.middle(2.2.0)
: 只当点击鼠标中键时触发。.passive(2.3.0)
: 以{ passive: true }
模式添加侦听器,表示listener
永远不会调用preventDefault()
。<!-- 方法处理器 -->
<button v-on:click="doThis"></button>
<!-- 动态事件 (2.6.0+) -->
<button v-on:[event]="doThis"></button>
<!-- 内联语句 -->
<button v-on:click="doThat('param',$event)"></button>
<!-- 缩写 -->
<button @click="doThis"></button>
<!-- 动态事件缩写 (2.6.0+) -->
<button @[event]="doThis"></button>
<!-- 停止冒泡 -->
<button @click.stop="doThis"></button>
<!-- 阻止默认行为 -->
<button @click.prevent="doThis"></button>
<!-- 阻止默认行为,没有表达式 -->
<form @submit.prevent></form>
<!-- 串联修饰符 -->
<button @click.stop.prevent="doThis"></button>
<!-- 键修饰符,键别名 -->
<input @keyup.enter="onEnter">
<!-- 键修饰符,键代码 -->
<input @keyup.13="onEnter">
<!-- 点击回调只会触发一次 -->
<button v-on:click.once="doThis"></button>
<!-- 对象语法 (2.4.0+) -->
<button v-on="{ mousedown: doThis,mouseup: doThat }"></button>
<!-- 自定义事件 -->
<my-component @my-event="handleThis"></my-component>
<!-- 内联语句 -->
<my-component @my-event="handleThis('param',$event)"></my-component>
<!-- 组件中的原生事件 -->
<my-component @click.native="onClick"></my-component>
// dev/src/compiler/parser/index.js line 23
export const onRE = /^@|^v-on:/
export const dirRE = process.env.VBIND_PROP_SHORTHAND
? /^v-|^@|^:|^\.|^#/
: /^v-|^@|^:|^#/
// ...
const dynamicArgRE = /^\[.*\]$/
// ...
export const bindRE = /^:|^\.|^v-bind:/
// dev/src/compiler/parser/index.js line 757
function processAttrs (el) {
const list = el.attrsList
let i,l,name,rawName,value,modifiers,syncGen,isDynamic
for (i = 0,l = list.length; i < l; i++) {
name = rawName = list[i].name
value = list[i].value
if (dirRE.test(name)) { // 匹配指令属性
// mark element as dynamic
el.hasBindings = true
// modifiers
modifiers = parseModifiers(name.replace(dirRE,'')) // 将修饰符解析
// support .foo shorthand syntax for the .prop modifier
if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
(modifiers || (modifiers = {})).prop = true
name = `.` + name.slice(1).replace(modifierRE,'')
} else if (modifiers) {
name = name.replace(modifierRE,'')
}
if (bindRE.test(name)) { // v-bind // 处理v-bind的情况
// ...
} else if (onRE.test(name)) { // v-on // 处理事件绑定
name = name.replace(onRE,'') // 将事件名匹配
isDynamic = dynamicArgRE.test(name) // 动态事件绑定
if (isDynamic) { // 如果是动态事件
name = name.slice(1,-1) // 去掉两端的 []
}
addHandler(el,false,warn,list[i],isDynamic) // 处理事件收集
} else { // normal directives // 处理其他指令
// ...
}
} else {
// literal attribute // 处理文字属性
// ...
}
}
}
// dev/src/compiler/helpers.js line 69
export function addHandler (
el: ASTElement,name: string,value: string,modifiers: ?ASTModifiers,important?: boolean,warn?: ?Function,range?: Range,dynamic?: boolean
) {
modifiers = modifiers || emptyObject
// passive 和 prevent 不能同时使用,具体是由passive模式的性质决定的
// 详细可以参阅 https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
// warn prevent and passive modifier
/* istanbul ignore if */
if (
process.env.NODE_ENV !== 'production' && warn &&
modifiers.prevent && modifiers.passive
) {
warn(
'passive and prevent can\'t be used together. ' +
'Passive handler can\'t prevent default event.',range
)
}
// 标准化click.right和click.middle,因为它们实际上不会触发。
// 从技术上讲,这是特定于浏览器的,但是至少目前来说,浏览器是唯一具有右键/中间点击的目标环境。
// normalize click.right and click.middle since they don't actually fire
// this is technically browser-specific,but at least for now browsers are
// the only target envs that have right/middle clicks.
if (modifiers.right) { // 将鼠标右键点击标准化 右键点击默认的是 contextmenu 事件
if (dynamic) { // 如果是动态事件
name = `(${name})==='click'?'contextmenu':(${name})` // 动态确定事件名
} else if (name === 'click') { // 如果不是动态事件且是鼠标右击
name = 'contextmenu' // 则直接替换为contextmenu事件
delete modifiers.right // 删除modifiers的right属性
}
} else if (modifiers.middle) { // 同样标准化处理鼠标中键点击的事件
if (dynamic) { // 如果是动态事件
name = `(${name})==='click'?'mouseup':(${name})` // 动态确定事件名
} else if (name === 'click') { // 如果不是动态事件且是鼠标中键点击
name = 'mouseup' // 处理为mouseup事件
}
}
// 下面是对捕获、一次触发、passive模式的modifiers处理,主要是为事件添加 !、~、& 标记
// 这一部分标记可以在Vue官方文档中查阅
// https://cn.vuejs.org/v2/guide/render-function.html#%E4%BA%8B%E4%BB%B6-amp-%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6
// check capture modifier
if (modifiers.capture) {
delete modifiers.capture
name = prependModifierMarker('!',dynamic)
}
if (modifiers.once) {
delete modifiers.once
name = prependModifierMarker('~',dynamic)
}
/* istanbul ignore if */
if (modifiers.passive) {
delete modifiers.passive
name = prependModifierMarker('&',dynamic)
}
// events 用来记录绑定的事件
let events
if (modifiers.native) { // 如果是要触发根元素原生事件则直接取得nativeEvents
delete modifiers.native
events = el.nativeEvents || (el.nativeEvents = {})
} else { // 否则取得events
events = el.events || (el.events = {})
}
// 将事件处理函数作为handler
const newHandler: any = rangeSetItem({ value: value.trim(),dynamic },range)
if (modifiers !== emptyObject) {
newHandler.modifiers = modifiers
}
// 绑定的事件可以多个,回调也可以多个,最终会合并到数组中
const handlers = events[name]
/* istanbul ignore if */
if (Array.isArray(handlers)) {
important ? handlers.unshift(newHandler) : handlers.push(newHandler)
} else if (handlers) {
events[name] = important ? [newHandler,handlers] : [handlers,newHandler]
} else {
events[name] = newHandler
}
el.plain = false
}
// dev/src/compiler/codegen/index.js line 42
export function generate (
ast: ASTElement | void,options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast,state) : '_c("div")'
return {
render: `with(this){return ${code}}`,// 即render字符串
staticRenderFns: state.staticRenderFns
}
}
// dev/src/compiler/codegen/index.js line 55
export function genElement (el: ASTElement,state: CodegenState): string {
// ...
let code
if (el.component) {
code = genComponent(el.component,el,state)
} else {
let data
if (!el.plain || (el.pre && state.maybeComponent(el))) {
data = genData(el,state)
}
const children = el.inlineTemplate ? null : genChildren(el,state,true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
// ...
}
// dev/src/compiler/codegen/index.js line 219
export function genData (el: ASTElement,state: CodegenState): string {
let data = '{'
// ...
// event handlers
if (el.events) {
data += `${genHandlers(el.events,false)},`
}
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents,true)},`
}
// ...
data = data.replace(/,$/,'') + '}'
// ...
return data
}
// dev/src/compiler/to-function.js line 12
function createFunction (code,errors) {
try {
return new Function(code) // 将render字符串转为render函数
} catch (err) {
errors.push({ err,code })
return noop
}
}
// dev/src/compiler/codegen/events.js line 3
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/
const fnInvokeRE = /\([^)]*?\);*$/
const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/
// dev/src/compiler/codegen/events.js line 7
// KeyboardEvent.keyCode aliases
const keyCodes: { [key: string]: number | Array<number> } = {
esc: 27,tab: 9,enter: 13,space: 32,up: 38,left: 37,right: 39,down: 40,'delete': [8,46]
}
// KeyboardEvent.key aliases
const keyNames: { [key: string]: string | Array<string> } = {
// #7880: IE11 and Edge use `Esc` for Escape key name.
esc: ['Esc','Escape'],tab: 'Tab',enter: 'Enter',// #9112: IE11 uses `Spacebar` for Space key name.
space: [' ','Spacebar'],// #7806: IE11 uses key names without `Arrow` prefix for arrow keys.
up: ['Up','ArrowUp'],left: ['Left','ArrowLeft'],right: ['Right','ArrowRight'],down: ['Down','ArrowDown'],// #9112: IE11 uses `Del` for Delete key name.
'delete': ['Backspace','Delete','Del']
}
}
// dev/src/compiler/codegen/events.js line 37
// #4868: modifiers that prevent the execution of the listener
// need to explicitly return null so that we can determine whether to remove
// the listener for .once
const genGuard = condition => `if(${condition})return null;`
const modifierCode: { [key: string]: string } = {
stop: '$event.stopPropagation();',prevent: '$event.preventDefault();',self: genGuard(`$event.target !== $event.currentTarget`),ctrl: genGuard(`!$event.ctrlKey`),shift: genGuard(`!$event.shiftKey`),alt: genGuard(`!$event.altKey`),meta: genGuard(`!$event.metaKey`),left: genGuard(`'button' in $event && $event.button !== 0`),middle: genGuard(`'button' in $event && $event.button !== 1`),right: genGuard(`'button' in $event && $event.button !== 2`)
}
// dev/src/compiler/codegen/events.js line 55
export function genHandlers (
events: ASTElementHandlers,isNative: boolean
): string {
const prefix = isNative ? 'nativeOn:' : 'on:'
let staticHandlers = ``
let dynamicHandlers = ``
for (const name in events) { // 遍历AST解析后的事件属性
const handlerCode = genHandler(events[name]) // 将事件对象转换成可拼接的字符串
if (events[name] && events[name].dynamic) {
dynamicHandlers += `${name},${handlerCode},`
} else {
staticHandlers += `"${name}":${handlerCode},`
}
}
staticHandlers = `{${staticHandlers.slice(0,-1)}}`
if (dynamicHandlers) {
return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0,-1)}])`
} else {
return prefix + staticHandlers
}
}
// dev/src/compiler/codegen/events.js line 96
function genHandler (handler: ASTElementHandler | Array<ASTElementHandler>): string {
if (!handler) {
return 'function(){}'
}
// 事件绑定可以多个,多个在解析AST树时会以数组的形式存在,如果有多个则会递归调用getHandler方法返回数组。
if (Array.isArray(handler)) {
return `[${handler.map(handler => genHandler(handler)).join(',')}]`
}
const isMethodPath = simplePathRE.test(handler.value) // 调用方法为 doThis 型
const isFunctionExpression = fnExpRE.test(handler.value) // 调用方法为 () => {} or function() {} 型
const isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE,'')) // 调用方法为 doThis($event) 型
if (!handler.modifiers) { // 没有修饰符
if (isMethodPath || isFunctionExpression) { // 符合这两个条件则直接返回
return handler.value
}
/* istanbul ignore if */
if (__WEEX__ && handler.params) {
return genWeexHandler(handler.params,handler.value)
}
return `function($event){${ // 返回拼接的匿名函数的字符串
isFunctionInvocation ? `return ${handler.value}` : handler.value
}}` // inline statement
} else { // 处理具有修饰符的情况
let code = ''
let genModifierCode = ''
const keys = []
for (const key in handler.modifiers) { // 遍历modifiers上记录的修饰符
if (modifierCode[key]) {
genModifierCode += modifierCode[key] // 根据修饰符添加对应js的代码
// left/right
if (keyCodes[key]) {
keys.push(key)
}
} else if (key === 'exact') { // 针对exact的处理
const modifiers: ASTModifiers = (handler.modifiers: any)
genModifierCode += genGuard(
['ctrl','shift','alt','meta']
.filter(keyModifier => !modifiers[keyModifier])
.map(keyModifier => `$event.${keyModifier}Key`)
.join('||')
)
} else {
keys.push(key) // 如果修饰符不是以上修饰符,则会添加到keys数组中
}
}
if (keys.length) {
code += genKeyFilter(keys) // 处理其他修饰符 即keyCodes中定义的修饰符
}
// Make sure modifiers like prevent and stop get executed after key filtering
if (genModifierCode) {
code += genModifierCode
}
// 根据三种不同的书写模板返回不同的字符串
const handlerCode = isMethodPath
? `return ${handler.value}($event)`
: isFunctionExpression
? `return (${handler.value})($event)`
: isFunctionInvocation
? `return ${handler.value}`
: handler.value
/* istanbul ignore if */
if (__WEEX__ && handler.params) {
return genWeexHandler(handler.params,code + handlerCode)
}
return `function($event){${code}${handlerCode}}`
}
}
// dev/src/compiler/codegen/events.js line 175
function genFilterCode (key: string): string {
const keyVal = parseInt(key,10)
if (keyVal) { // 如果key是数字,则直接返回$event.keyCode!==${keyVal}
return `$event.keyCode!==${keyVal}`
}
const keyCode = keyCodes[key]
const keyName = keyNames[key]
// 返回_k函数,它的第一个参数是$event.keyCode,
// 第二个参数是key的值,
// 第三个参数就是key在keyCodes中对应的数字。
return (
`_k($event.keyCode,` +
`${JSON.stringify(key)},` +
`${JSON.stringify(keyCode)},` +
`$event.key,` +
`${JSON.stringify(keyName)}` +
`)`
)
}
// dev/src/core/vdom/patch.js line 33
const hooks = ['create','activate','update','remove','destroy']
// dev/src/core/vdom/patch.js line 125
function createElm (
vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index
) {
// ...
if (isDef(data)) {
invokeCreateHooks(vnode,insertedVnodeQueue)
}
// ...
}
// dev/src/core/vdom/patch.js line 303
// 在之前cbs经过处理
// 这里cbs.create包含如下几个回调:
// updateAttrs、updateClass、updateDOMListeners、updateDOMProps、updateStyle、update、updateDirectives
function invokeCreateHooks (vnode,insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode,vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode,vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
// dev/src/platforms/web/runtime/modules/events.js line 105
function updateDOMListeners (oldVnode: VNodeWithData,vnode: VNodeWithData) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) { // on是事件指令的标志
return
}
// 新旧节点不同的事件绑定解绑
const on = vnode.data.on || {}
const oldOn = oldVnode.data.on || {}
// 拿到需要添加事件的真实DOM节点
target = vnode.elm
// normalizeEvents是对事件兼容性的处理
normalizeEvents(on)
// 调用updateListeners方法,并将on作为参数传进去
updateListeners(on,oldOn,add,remove,createOnceHandler,vnode.context)
target = undefined
}
// dev/src/core/vdom/helpers/update-listeners.js line line 53
export function updateListeners (
on: Object,oldOn: Object,add: Function,remove: Function,createOnceHandler: Function,vm: Component
) {
let name,def,cur,old,event
for (name in on) { // 遍历事件
def = cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
/* istanbul ignore if */
if (__WEEX__ && isPlainObject(def)) {
cur = def.handler
event.params = def.params
}
if (isUndef(cur)) { // 事件名非法的报错处理
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),vm
)
} else if (isUndef(old)) { // 旧节点不存在
if (isUndef(cur.fns)) { // createFunInvoker返回事件最终执行的回调函数
cur = on[name] = createFnInvoker(cur,vm)
}
if (isTrue(event.once)) { // 只触发一次的事件
cur = on[name] = createOnceHandler(event.name,event.capture)
}
// 执行真正注册事件的执行函数
add(event.name,event.capture,event.passive,event.params)
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
for (name in oldOn) { // 旧节点存在,解除旧节点上的绑定事件
if (isUndef(on[name])) {
event = normalizeEvent(name)
// 移除事件监听
remove(event.name,oldOn[name],event.capture)
}
}
}
// dev/src/platforms/web/runtime/modules/events.js line 32
// 在执行完回调之后,移除事件绑定
function createOnceHandler (event,handler,capture) {
const _target = target // save current target element in closure
return function onceHandler () {
const res = handler.apply(null,arguments)
if (res !== null) {
remove(event,onceHandler,capture,_target)
}
}
}
// dev/src/platforms/web/runtime/modules/events.js line 46
function add (
name: string,handler: Function,capture: boolean,passive: boolean
) {
// async edge case #6566: inner click event triggers patch,event handler
// attached to outer element during patch,and triggered again. This
// happens because browsers fire microtask ticks between event propagation.
// the solution is simple: we save the timestamp when a handler is attached,// and the handler would only fire if the event passed to it was fired
// AFTER it was attached.
if (useMicrotaskFix) {
const attachedTimestamp = currentFlushTimestamp
const original = handler
handler = original._wrapper = function (e) {
if (
// no bubbling,should always fire.
// this is just a safety net in case event.timeStamp is unreliable in
// certain weird environments...
e.target === e.currentTarget ||
// event is fired after handler attachment
e.timeStamp >= attachedTimestamp ||
// bail for environments that have buggy event.timeStamp implementations
// #9462 iOS 9 bug: event.timeStamp is 0 after history.pushState
// #9681 QtWebEngine event.timeStamp is negative value
e.timeStamp <= 0 ||
// #9448 bail if event is fired in another document in a multi-page
// electron/nw.js app,since event.timeStamp will be using a different
// starting reference
e.target.ownerDocument !== document
) {
return original.apply(this,arguments)
}
}
}
target.addEventListener(
name,supportsPassive
? { capture,passive }
: capture
)
}
// dev/src/platforms/web/runtime/modules/events.js line 92
function remove (
name: string,_target?: HTMLElement
) {
(_target || target).removeEventListener(
name,handler._wrapper || handler,capture
)
}
https://github.com/WindrunnerMax/EveryDay
https://cn.vuejs.org/v2/api/#v-on
https://juejin.im/post/6844903919290679304
https://juejin.im/post/6844904061897015310
https://juejin.im/post/6844904126250221576
https://segmentfault.com/a/1190000009750348
https://blog.csdn.net/weixin_41275295/article/details/100549145
https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
https://github.com/liutao/vue2.0-source/blob/master/%E4%BA%8B%E4%BB%B6%E5%A4%84%E7%90%86.md
本站采用系统自动发货方式,付款后即出现下载入口,如有疑问请咨询在线客服!
售后时间:早10点 - 晚11:30点Copyright © 2024 jiecseo.com All rights reserved. 粤ICP备18085929号
欢迎光临【捷杰建站】,本站所有资源仅供学习与参考,禁止用于商业用途或从事违法行为!
技术营运:深圳市晟艺互动传媒有限公司