Vue事件源碼分析

vue事件簡介

vue事件有兩種:dom事件和自定義事件,前者使用在dom元素上,可使用v-on或@來監聽事件,自定義事件用於組件上,方便組件間的通訊,若是在組件上使用原生事件,須要加 .native 修飾符,下面咱們從源碼分析下vue事件運行的原理vue

事件的編譯

//在節點編譯過程當中會執行processattrs
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
      //parseModifiers方法會對有修飾符的屬性,進行處理,獲得modifier
      modifiers = parseModifiers(name.replace(dirRE, ''))
      // support .foo shorthand syntax for the .prop modifier
      
      //解析事件指令,對name中'v-on'字段檢測,知足的話把這個字段也去掉,name就變成click,調用addHandler。
      //addHandler,給AST節點添加event屬性,並根據modifer解析出來的標記給name上打標記
      if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
        (modifiers || (modifiers = {})).prop = true
        name = `.` + name.slice(1).replace(modifierRE, '')
        //若是有modifer,把name中modifer相關字段去掉
      } else if (modifiers) {
        name = name.replace(modifierRE, '')
      }
    ...
        
        addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
   ...
  }
}
複製代碼

addHandler 根據modifer.native判斷原生事件仍是普通事件。接下來構造event對象,若是有 .native ,構造nativeEvents對象,不然構造events對象。按照name對事件作歸類,並把回調函數的字符串保留到對應的事件中node

function addHandler (
    el,
    name,
    value,
    modifiers,
    important,
    warn,
    range,
    dynamic
  ) {
  ...
//根據modifer.native判斷原生事件仍是普通事件,主要是生成events或nativeEvents屬性
    var events;
    if (modifiers.native) {
      delete modifiers.native;
      events = el.nativeEvents || (el.nativeEvents = {});
    } else {
      events = el.events || (el.events = {});
    }

...
    el.plain = false;
  }
複製代碼

genData函數中根據AST元素節點上的events和nativeEvents生成data數據,genHandlers 根據isNative判斷res的值爲'nativeOn'/ 'on'。遍歷events對象,拼接genHandler會生成handler的代碼,拼接出一個JSON代碼對象。bash

{
 on: {"select": selectHandler},
 nativeOn: {"click": function($event) {
     $event.preventDefault();
     return clickHandler($event)
   }
 }
}
複製代碼

事件的運行

dom事件

dom事件的添加和移除,實際上就是調用原生 addEventListener 和 removeEventListenerapp

function add (
  name: string,
  handler: Function,
  capture: boolean,
  passive: boolean
) {
 ...
  target.addEventListener(
    name,
    handler,
    supportsPassive
      ? { capture, passive }
      : capture
  )
}

function remove (
  name: string,
  handler: Function,
  capture: boolean,
  _target?: HTMLElement
) {
  (_target || target).removeEventListener(
    name,
    handler._wrapper || handler,
    capture
  )
}
複製代碼

自定義事件

對於自定義事件和原生 DOM 事件處理的差別就在事件添加和刪除的實現上,自定義事件 add 和 remove 的實現dom

var target;
//添加事件是在裏面調用來$on方法來實現
function add (event, fn) {
  target.$on(event, fn);
}
//移除事件是在裏面調用來$off方法來實現
function remove$1 (event, fn) {
  target.$off(event, fn);
}
複製代碼

自組件觸發自定義事件時,回掉函數是在父組件內執行的。函數

//createComponent。在建立組件vnode以前,會對事件作處理,
//會把data.on(自定義事件)賦值給listeners,listeners在組件實例化成VNode時會做爲參數傳入
//拿到 listeners 後,執行 updateComponentListeners(vm, listeners) 方法:
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }
  ...
  //它把 data.on 賦值給了 listeners,把 data.nativeOn賦值給了 data.on,這樣在組件上也能夠定義原生事件,
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn
    ...
  return vnode
}
複製代碼

Vue爲咱們提供了四個自定義事件API,分別是$on$once$off$emit源碼分析

on事件

function eventsMixin (Vue) {
  var hookRE = /^hook:/;
  
  //$on方法用來在vm實例上監聽一個自定義事件,該事件可用$emit觸發。
  //當執行 vm.$on(event,fn) 的時候,按事件的名稱 event 
  //把回調函數 fn 存儲起來 vm._events[event].push(fn)。
  Vue.prototype.$on = function (event, fn) {
    var vm = this;
    if (Array.isArray(event)) {
      for (var i = 0, l = event.length; i < l; i++) {
        vm.$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
      if (hookRE.test(event)) {
        vm._hasHookEvent = true;
      }
    }
    return vm
  };
複製代碼

once事件

//vm.$once執行的時候,內部就是執行 vm.$on
//而且當回調函數執行一次後再經過 vm.$off 移除事件的回調,這樣就確保了回調函數只執行一次
Vue.prototype.$once = function (event, fn) {
  var vm = this;
  function on () {
    vm.$off(event, on);
    fn.apply(vm, arguments);
  }
  on.fn = fn;
  vm.$on(event, on);
  return vm
};
複製代碼

off事件

//當執行 vm.$off(event,fn) 的時候會移除指定事件名 event 和指定的 fn ,根據傳入的參數移除相應的監聽事件,
  Vue.prototype.$off = function (event, fn) {
    var vm = this;
    // all
    if (!arguments.length) {
      vm._events = Object.create(null);
      return vm
    }
    // array of events
    if (Array.isArray(event)) {
      for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
        vm.$off(event[i$1], fn);
      }
      return vm
    }
    ...
複製代碼

emit事件

//執行 vm.$emit的時候,根據事件名 event 找到全部的回調函數
//let cbs = vm._events[event],而後遍歷執行全部的回調函數,根據相應的事件名
//執行不一樣的回掉函數
  Vue.prototype.$emit = function (event) {
    var vm = this;
    ···
    return vm
  };
}
複製代碼
相關文章
相關標籤/搜索