One Last You Jen Bird
  1. 1 One Last You Jen Bird
  2. 2 かかってこいよ NakamuraEmi
  3. 3 Warcry mpi
  4. 4 Libertus Chen-U
  5. 5 Life Will Change Lyn
  6. 6 BREAK IN TO BREAK OUT Lyn
  7. 7 Flower Of Life 发热巫女
  8. 8 Hypocrite Nush
  9. 9 Time Bomb Veela
  10. 10 Last Surprise Lyn
2017-11-22 12:26:24

Vue原理解析——事件系统

Vue中的事件分为Dom事件和Vue自定义事件两种。这两者的区别是什么呢?


Dom事件

v-on作为Dom事件的指令入口,要经过模板编译和指令注册两部分。先看编译部分:

const onRE = /^v-on:|^@/
//编译元素中的指令
function compileDirectives (attrs, options) {
  ...
  //关于“事件绑定”的处理
  if (onRE.test(name)) {
    arg = name.replace(onRE, '')
    //添加一个“on”指令, publicDirectives.on为该指令的所有“钩子函数”等属性和方法
    //“钩子函数”的介绍可参考Vue文档中自定义指令的介绍
    pushDir('on', publicDirectives.on)
  }
  ...
}

在publicDirectives.on中定义了on指令的所有属性和方法,其中的update方法进行了事件的绑定处理,如下:

update (handler) {
  // "v-on"属性值为空的容错处理,
  // 例如 @mousedown.prevent
  if (!this.descriptor.raw) {
    handler = function () {}
  }
  // "v-on"属性值不为空但不是函数,报错
  if (typeof handler !== 'function') {
    process.env.NODE_ENV !== 'production' && warn(
      'v-on:' + this.arg + '="' +
      this.expression + '" expects a function value, ' +
      'got ' + handler,
      this.vm
    )
    return
  }
  // 事件修饰符(modifiers)处理
  if (this.modifiers.stop) {
    // 返回一个新的方法,先执行event.stopPropagation(),再执行事件绑定事件
    handler = stopFilter(handler)
  }
  if (this.modifiers.prevent) {
    // 返回一个新的方法,先执行event.preventDefault(),再执行事件绑定事件
    handler = preventFilter(handler)
  }
  if (this.modifiers.self) {
    // 返回一个新的方法,如果触发元素为当前元素,才执行事件绑定事件
    handler = selfFilter(handler)
  }
  // 事件按键修饰符处理
  var keys = Object.keys(this.modifiers)
    .filter(function (key) {
      //排除事件修饰符
      return key !== 'stop' &&
        key !== 'prevent' &&
        key !== 'self' &&
        key !== 'capture'
    })
  if (keys.length) {
    // 返回一个新的方法,如果触发元素的keyCode等于按键修饰符中的code,才执行事件绑定事件
    handler = keyFilter(handler, keys)
  }
  // 避免重复绑定,先解绑
  this.reset()
  this.handler = handler
  if (this.iframeBind) {
    //处理iframe中的事件绑定
    this.iframeBind()
  } else {
    //调用on方法进行事件绑定
    on(
      this.el,
      this.arg,
      this.handler,
      this.modifiers.capture
    )
  }
}

最后的on方法就是原生Dom二级事件的绑定了。

on (el, event, cb, useCapture) {
  el.addEventListener(event, cb, useCapture)
}

自定义事件

自定义事件只能绑定在Vue对象上,可以使用v-on指令,也可以使用Vue提供的四个事件API去处理:$on,$once,$off,$emit。

前面我们说过Vue初始化入口 ,在其中就包含对事件对象的初始化方法_initEvents。

initEvents (vm: Component) {
  /*在vm上创建一个_events对象,用来存放事件。*/
  vm._events = Object.create(null)
  /*这个bool标志位来表明是否存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
  vm._hasHookEvent = false
  // init parent attached events
  /*初始化父组件attach的事件*/
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

然后通过$on方法将事件处理函数推入_events对象中管理。

Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this

    /*如果是数组的时候,则递归$on,为每一个成员都绑定上方法*/
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$on(event[i], fn)
      }
    } else {
      (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
      /*这里在注册事件的时候标记bool值也就是个标志位来表明存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

$emit方法触发事件处理函数。

Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      /*将类数组的对象转换成数组*/
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      /*遍历执行*/
      for (let i = 0, l = cbs.length; i < l; i++) {
        cbs[i].apply(vm, args)
      }
    }
    return vm
  }

可以看到Vue自定义事件的原理就是观察者模式,通过在Vue实例维护一个事件对象去管理即可。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「Vue」 标签。