您好,欢迎来到化拓教育网。
搜索
您的当前位置:首页vue的原生事件绑定流程

vue的原生事件绑定流程

来源:化拓教育网
vue的原⽣事件绑定流程

本⽂可能需要对vue,从编译模板到⽣成dom的流程具有⼀定的熟悉程度,可能才能够明⽩。同时不排除作者有理解出错的地⽅,⼤家在学习的过程中可以进⾏参考。

简单流程

从⼀个简单的例⼦⼊⼿

假如我们在模板上定义了⼀个事件,那么我们知道,vue会对我们写的模板进⾏解析,⽣成AST。如果你在模板上绑定了事件,那么AST上会有⼀个叫做events或者nativeEvents的属性。。⼤致长这个样⼦

{ 'click': { value: '事件绑定函数名称', modifiers: {} } }

根据不同的修饰符,他会长成不同的形式,上⾯只是其中⼀个种形式。然后到⽣成代码阶段。vue会解析这个AST树。最终经过代码的处理会变成vnode。从模板编译到⽣成vnode的详细过程,本⽂不进⾏介绍。

vnode解析

本⽂着重关注vnode解析阶段对事件的解析。我们知道⾸次渲染,或者是更新。都会发⽣在createPatchFunction,我们已Web平台为例。这个函数在vue源码的src/core/vdom/patch.js下。它⼤致长这样

export function createPatchFunction (backend) { let i, j

 const cbs = {}  

const { modules, nodeOps } = backend for (i = 0; i < hooks.length; ++i) { cbs[hooks[i]] = []

for (j = 0; j < modules.length; ++j) { if (isDef(modules[j][hooks[i]])) {

cbs[hooks[i]].push(modules[j][hooks[i]]) } } }

//省略若⼲

return function patch (oldVnode, vnode, hydrating, removeOnly) { if (isUndef(vnode)) {

if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return }

let isInitialPatch = false

const insertedVnodeQueue = []

if (isUndef(oldVnode)) { //⾸次渲染

isInitialPatch = true

createElm(vnode, insertedVnodeQueue) } else { ... } } }}

当⾸次渲染时候调⽤createPatchFunction。调⽤createPatchFunction本质上就是调⽤patch。然后进⼊patch后就进⼊调⽤createElm准备⽣成真正的dom结点。我们可以先来看看哪⾥调⽤了这个createPatchFunction。他在platform/web/runtime/patch.js中被调⽤。

import * as nodeOps from 'web/runtime/node-ops'

import { createPatchFunction } from 'core/vdom/patch'import baseModules from 'core/vdom/modules/index'

import platformModules from 'web/runtime/modules/index'

// the directive module should be applied last, after all// built-in modules have been applied.

const modules = platformModules.concat(baseModules)//module// [

// attrs,// klass,// events,// domProps,// style,// transition// ]

//baseModules// [// ref,

// directives// ]

export const patch: Function = createPatchFunction({ nodeOps, modules })

可以看到createPatchFunction执⾏了之后再赋值给patch。这个patch就是上⾯返回的patch函数。它在其他地⽅被⽤在渲染视图上,这⾥不讲述。

那么为什么我们要看这个函数的出⽣地呢?因为它的参数⼗分重要。他有两个参数,第⼀个参数是存放操作dom节点的⽅法,终点关注modules。从上⾯的impot⼤家可以找下module的出处。它由两个数字拼接起来,其中关注⼀下数组有⼀个元素叫做events。我们的事件添加就发⽣在这⾥。

我们继续寻找⼀下events的出⽣地,别的属性和事件关联不⼤,我们重点看events。根据import我们找到events的出⽣地。然后我们关注events⽂件最后它导出了⼀些东西。好了我们记住它导出了⼀个对象,然后对象有⼀个属性叫做create。那么modules经过进⼀步解析长成下⾯这样,我们回到createPatchFunction解析

export default {

create: updateDOMListeners, update: updateDOMListeners}

//modules[ ...{

create: updateDOMListeners, update: updateDOMListeners}...]

我们注意createPatchFunction。createPatchFunction接受⼀个参数叫做backend。然后在函数开头,对backend进⾏解构,就是上⾯代码的nodeOps和modules参数。

const { modules, nodeOps } = backendfor (i = 0; i < hooks.length; ++i) { cbs[hooks[i]] = []

for (j = 0; j < modules.length; ++j) { if (isDef(modules[j][hooks[i]])) {

cbs[hooks[i]].push(modules[j][hooks[i]]) } } }

解构完之后进⼊for循环。在createPatchFunction开头定义了⼀个cbs对象。for循环遍历⼀个叫hooks的数组。hooks是这样的,它定义在本⽂件开头

const hooks = ['create', 'activate', 'update', 'remove', 'destroy'] 

我们看下这个for循环意图就是在cbs上定义⼀系列和hooks元素相同的属性,然后键值是⼀个数组,然后数组内容是modules⾥⾯的⼀些内容。最后cbs⼤致是这样的。cbs: {'create': [], 'update': []...}

 结合modules的结构看,create的键值⾥⾯会有updateDOMListeners⽅法,这个⽅法是真正添加事件的⽅法,那么他在哪⾥被调⽤我们继续看。好了我们回到createPatchFunction的return值patch函数看。当我们进⼊⾸次渲染的时候,会执⾏到patch函数⾥⾯的createElm⽅法。

return function patch (oldVnode, vnode, hydrating, removeOnly) { if (isUndef(vnode)) {

if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return }

let isInitialPatch = false

const insertedVnodeQueue = []

if (isUndef(oldVnode)) { //⾸次渲染

isInitialPatch = true

createElm(vnode, insertedVnodeQueue) } else { ... } }}

我们看看createElm做了什么事情。

function createElm ( vnode,

insertedVnodeQueue, parentElm, refElm, nested,

ownerArray, index ) { ...

createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) {

   //这⾥是处理事件系统的

  invokeCreateHooks(vnode, insertedVnodeQueue) } ...}

为了让⼤家看清晰,我删掉了很多,⼤家可以⾃⼰打开⼀份源码对⽐着看。我们关注⼀个叫invokeCreateHooks函数。这⾥就是真正准备进⾏原⽣事件绑定的⼊⼝!!

我们看看invokeCreateHooks函数做了什么。它的代码⽐较短。

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) } }

我们关注第⼀个for循环。我看可以看到他再遍历cb.create数组⾥⾯的内容。然后把cbs.create⾥⾯的函数全部都执⾏⼀次,我们回忆⼀下cbs.create⾥⾯有什么内容,其中⼀个函数就是updateDOMListeners。

在这⾥开始执⾏updateDOMListeners。我们现在看updateDOMListeners做了什么。这个⽅法定义在了platform/web/runtime/modules/events.js中,

//events.js....

let target: any....

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指向dom结点 target = vnode.elm normalizeEvents(on)

updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context) target = undefined}

第⼀个if是根据vnode判断是否有定义⼀个点击事件。有的话就继续执⾏,没有就return。然后给on进⾏赋值。on⼤致会长成这样

然后进⾏⼀些赋值操作。其中关注target。vue把vnode.elm赋值给target,我们知道elm这个属性就是指向vnode所对应的真实dom结点,所以这⾥就是把我们要绑定事件的dom结点进⾏缓存。

然后执⾏normalizeEvents,他是对on继续进⾏⼀些处理,我们暂不关⼼他做什么,这对于我们理解事件绑定流程影响不⼤。接下来执⾏updateListeners⽅法。看看它做了什么

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)) { // // {

// 'click': invoker() // }

cur = on[name] = createFnInvoker(cur, vm) }

if (isTrue(event.once)) {

cur = on[name] = createOnceHandler(event.name, cur, event.capture) }

add(event.name, cur, 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)

} }}

重点关注add⽅法,它还是在platform/web/runtime/modules/events.js中

function add ( name: string,

handler: Function, capture: boolean, passive: boolean) {

if (useMicrotaskFix) {

const attachedTimestamp = currentFlushTimestamp //handle, original值 //ƒ invoker() // fns: ƒ ()

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指向dom结点,

//执⾏到这⾥的时候target已经被赋值了 target.addEventListener( name, handler,

supportsPassive

? { capture, passive } : capture )}

这个⽅法最后就通过addEventListener把事件绑定到dom上。

最后

  这⾥很多的细节其实都没有提及,只是⼤概的把整个流程进⾏了梳理。可能了解不深的读者可能还是看不懂。⼤家可以根据⾃⼰的情况对本⽂进⾏参考。⼤家也可以⾃⼰创建⼀个vue项⽬,然后在⾕歌浏览器中对vue下断点,⼀步⼀步执⾏,那么整个流程会更加清晰。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo9.cn 版权所有 赣ICP备2023008801号-1

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务