vue 循环中绑定事件是否需要事件委托

 

事件委托是在js中减少事件绑定的方式,是一种常用的js优化方法。曾经review其他人的代码,发现很多同学在循环的时候直接在循环元素上绑定事件。那么vue 循环中绑定事件是否需要事件委托?。

通过打印出下面简单实例编译出来的render函数

<div id="demo">
    <a v-for="item in [1,2]" @click="clickFn">1</a>
</div>

我们得到下面的结果: 

ƒanonymous() {
    with(this) {
        return _c('div', {
            attrs: {
                "id": "demo"
            }
        },

        // 这部分就是循环
        _l(([1, 2]),
        function(item) {
            return _c('a', {
                on: {
                    "click": clickFn
                }
            },
            [_v("1")])
        }), 0)
    }
}

查找源代码得_l 的函数定义如下:


export function renderList (
  val: any,
  render: (
    val: any,
    keyOrIndex: string | number,
    index?: number
  ) => VNode
): ?Array<VNode> {
  let ret: ?Array<VNode>, i, l, keys, key
  if (Array.isArray(val) || typeof val === 'string') {
    ret = new Array(val.length)
    for (i = 0, l = val.length; i < l; i++) {
      ret[i] = render(val[i], i)
    }
  } 

  ... 省略了不相关分支代码

  (ret: any)._isVList = true
  return ret
}

也就是说这东西会return一个包含两个a标签,包含on属性的vnode的数组

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false 

  // 初始化父类绑定事件
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}


let target: any

// 添加事件
function add (event, fn) {
  target.$on(event, fn)
}

export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm

  // 更新事件监听
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
  target = undefined
}

export function updateListeners (
  on: Object, // 绑定事件
  oldOn: Object, // 旧绑定事件
  add: Function, // 添加时间绑定函数
  remove: Function, // 移除事件监听
  createOnceHandler: Function, // 创建once监听帮助函数
  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)) {
        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)
    }
  }
}

export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  
  // on方法定义
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this

    // 如果事件是数组,遍历并递归
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      // 将事件处理函数push到组件_events中
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

以上是事件绑定相关的代码,一些主要步骤做了注释,可以看出最终事件绑定是通过$on进行的,$on 最终的绑定事件代码为: (vm._events[event] || (vm._events[event] = [])).push(fn) ,就是将事件处理函数push到_events数组中。整个事件绑定过程,以及最后事件实际push到_events中的过程都没有看到有将子元素事件绑定到父元素的相关代码。

 

function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    if (isDef(vnode.elm) && isDef(ownerArray)) {
      vnode = ownerArray[index] = cloneVNode(vnode)
    }

    vnode.isRootInsert = !nested // for transition enter check
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
      // 省略部分代码

      // nodeOps 定义在 src\platforms\web\runtime\node-ops.js 中,主要是真正的dom操作。
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag) // 创建一个具有指定的命名空间URI和限定名称的元素。
        : nodeOps.createElement(tag, vnode) // 其实就是document.createElement(tagName)
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
        // 省略部分代码
      } else {
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        // 插入父级元素
        insert(parentElm, vnode.elm, refElm)
      }

      if (process.env.NODE_ENV !== 'production' && data && data.pre) {
        creatingElmInVPre--
      }
    } 
    // 省略部分代码
  }

虚拟DOM转化成真实DOM,该过程最主要的由createElm实现,其中 nodeOps.createElement(tag, vnode) 内部通过document.createElement(tagName) 创建真正的DOM元素。虚拟dom转化成真实DOM的过程也没有看到任何事件委托的实现。虚拟dom转化成真实dom的过程没有详细些,网上已经有人分析了这个过程,可参考:https://www.jianshu.com/p/49042bd8fef4

结论:以上从模板编译成render函数,最终返回的vnode数组;到事件绑定的实现;到虚拟都转化成真实dom。三个方面,都未曾看到有任何内部自动事件委托的实现。所以个人认为:循环列表中,如果元素较多,最好还是采用事件委托。

以上是个人分析过程,如有错误,欢迎批评指正。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值