绑定methods上下文
function initMethods (vm: Component, methods: Object) {
for (const key in methods) {
// 将method的this绑定为当前构建对象
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
事件订阅
原生事件订阅
示例代码
it('should bind event to a method', () => {
vm = new Vue({
el,
template: '<div v-on:click="foo"></div>',
methods: { foo: spy }
})
triggerEvent(vm.$el, 'click')
expect(spy.calls.count()).toBe(1)
const args = spy.calls.allArgs()
const event = args[0] && args[0][0] || {}
expect(event.type).toBe('click')
})
编译优化后的render
(function anonymous(
) {
with(this){return _c('div',{on:{"click":foo}})}
})
patch过程中会回调create|update hook, 在hook中完成对事件的处理
src/platforms/web/runtime/modules/events.js
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
const on = vnode.data.on || {}
const oldOn = oldVnode.data.on || {}
target = vnode.elm
normalizeEvents(on)
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
target = undefined
}
export default {
create: updateDOMListeners,
update: updateDOMListeners
}
自定义事件处理
示例代码
it('should bind to a child component', () => {
vm = new Vue({
el,
template: '<bar @custom="foo"></bar>',
methods: { foo: spy },
components: {
bar: {
template: '<span>Hello</span>'
}
}
})
vm.$children[0].$emit('custom', 'foo', 'bar')
expect(spy).toHaveBeenCalledWith('foo', 'bar')
})
编译优化后的render
(function anonymous(
) {
with(this){return _c('bar',{on:{"custom":foo}})}
})
事件相关订阅属于子组件bar的属性,但回调函数属于父组件
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
//创建bar vnode
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
// componentOptions
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
// 初始化子组件
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
// 获取父组件的订阅对象,即data.on
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
// 初始化事件订阅
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
// 完成事件的订阅
updateComponentListeners(vm, listeners)
}
}
带native的处理过程
示例
it('should be able to bind native events for a child component', () => {
vm = new Vue({
el,
template: '<bar @click.native="foo"></bar>',
methods: { foo: spy },
components: {
bar: {
template: '<span>Hello</span>'
}
}
})
vm.$children[0].$emit('click')
expect(spy).not.toHaveBeenCalled()
triggerEvent(vm.$children[0].$el, 'click')
expect(spy).toHaveBeenCalled()
})
编译后代码
(function anonymous(
) {
with(this){return _c('bar',{nativeOn:{"click":function($event){return foo($event)}}})}
})
子组件编译后代码
(function anonymous(
) {
with(this){return _c('span',[_v("Hello")])}
})
//src/core/vdom/patch.js
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
vnode.data.pendingInsert = null
}
// placeholder 获取组件渲染结果
vnode.elm = vnode.componentInstance.$el
if (isPatchable(vnode)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
} else {
// empty component root.
// skip all element-related modules except for ref (#3455)
registerRef(vnode)
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode)
}
}
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
const on = vnode.data.on || {}
const oldOn = oldVnode.data.on || {}
// 在根节点上订阅事件
target = vnode.elm
normalizeEvents(on)
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
target = undefined
}