angular事件深刻

之前對事件都是能用就行,本週對事件進行了一下較全面的學習, 在這裏記錄一下。javascript

什麼是事件

事件能夠理解爲行爲,如對button的點擊,事件的本質是一個函數,他接收一個event對象。
每一個事件會產生一個Event對象,Event 對象表明事件的狀態,好比事件在其中發生的元素、鍵盤按鍵的狀態、鼠標的位置、鼠標按鈕的狀態。html

事件傳播機制

事件的傳播機制能夠分爲冒泡捕獲java

冒泡

官方的定義就是從最特定的事件目標到最不特定的事件目標。express

意思就是說,假如用戶單擊了一個元素,該元素擁有一個click事件,那麼一樣的事件也將會被它的祖先觸發,這個事件從該元素開始一直冒泡到DOM樹的最上層,這一過程稱爲事件冒泡

捕獲

事件捕獲和事件是相反的,也就是說,當用戶觸發了一個事件的時候,這個事件是從DOM樹的最上層開始觸發一直到捕獲到事件源。

事件流

事件流是事件傳播機制的標準segmentfault

因爲微軟和網景亂搞,後來必需要爲事件傳播機制,制定一個標準,由於事件捕獲是網景公司開發出來的,而事件冒泡是由微軟公司開發出來的,它們都想要本身的技術成爲標準,因此致使這兩個公司總是幹架,制定標準的人爲了避免讓它們幹架,因此研發了事件流。

事件流的使用

事件流的使用就是咱們經常使用的addEventListener了,標準用法以下數組

dom對象.addEventListener(事件類型, 回調函數, 事件機制)這裏的事件類型表示你要使用哪一種事件類型好比 click, 回調函數裏面寫着觸發此事件你要作什麼事情, 事件機制分爲冒泡和捕獲,若是爲 false表示事件冒泡,爲 true表示事件捕獲

發現本身之前一直使用的都是不標準的寫法緩存

dom對象.attachEvent(eventType, fn)第一個參數表示事件類型,第二個是回調,這種的事件傳播機制是冒泡

關於js的事件就到這裏了,若是想要了解js的兼容寫法等能夠查看這篇文章angular2

angular事件

Angular 組件和 DOM 元素經過事件與外部進行通訊, Angular 事件綁定語法對於組件和 DOM 元素來講是相同的 - (eventName)="expression" :dom

<button (click)="onClick()">Click</button>

angular 事件特色:ide

Angular 支持 DOM 事件冒泡機制,但不支持自定義事件的冒泡

angular是如何處理事件的

下面咱們來看看angular是如何處理事件的,下面的源碼閱讀過程來自Angular 4.x EventManager & Custom EventManagerPlugin

listen

Angular 在解析 DOM 樹的時候,對於事件綁定它會調用DomRenderer實例的listen()方法,進行事件綁定,listen()方法具體實現以下:

// angular2/packages/platform-browser/src/dom/dom_renderer.ts
class DefaultDomRenderer2 implements Renderer2 {
    ....
    listen(target: 'window'|'document'|'body'|any, event: string, 
      callback: (event: any) => boolean):
          () => void {
        checkNoSyntheticProp(event, 'listener');
        if (typeof target === 'string') {
          return <() => void>this.eventManager.addGlobalEventListener(
              target, event, decoratePreventDefault(callback));
        }
        return <() => void>this.eventManager.addEventListener(
                   target, event, decoratePreventDefault(callback)) as() => void;
    }
}

經過源碼咱們發現,無論走哪條分支,最終都是調用this.eventManager對象的方法設置事件監聽。這裏的this.eventManager是什麼?它是 Angular 中的事件管理器EventManager

EventManager (事件管理器)

在 Angular 中全部的事件綁定都是由一個事件管理器來驅動,事件管理器自己由多個事件插件提供支持。Angular 中內置的事件插件以下:

  • KeyEventsPlugin - 處理鍵盤事件
  • HammerGesturesPlugin - 處理手勢
  • DomEventsPlugin - 處理 DOM 事件

EventManager 類

// angular2/packages/platform-browser/src/dom/events/event_manager.ts
export class EventManager {
  // EventManagerPlugin列表
  private _plugins: EventManagerPlugin[]; 
  // 緩存已匹配的eventName與對應的插件
  private _eventNameToPlugin = new Map<string, EventManagerPlugin>();

  constructor(
    @Inject(EVENT_MANAGER_PLUGINS) plugins: EventManagerPlugin[], 
    private _zone: NgZone) {
        plugins.forEach(p => p.manager = this);
        /**
         * {provide: EVENT_MANAGER_PLUGINS, useClass: DomEventsPlugin, multi: true},
         * {provide: EVENT_MANAGER_PLUGINS, useClass: KeyEventsPlugin, multi: true},
         * {provide: EVENT_MANAGER_PLUGINS, useClass: HammerGesturesPlugin, multi: true}
         * 
         * slice(): 建立新的plugins數組
         * reverse(): 讓DomEventsPlugin插件做爲列表最後一項,由於它可以處理全部的事件。
        */
        this._plugins = plugins.slice().reverse();
  }

  // 獲取能處理eventName的插件,並調用對應插件提供的addEventListener()方法
  addEventListener(element: HTMLElement, eventName: string,
    handler: Function): Function {
        const plugin = this._findPluginFor(eventName);
        return plugin.addEventListener(element, eventName, handler);
  }

  // 獲取能處理eventName的插件,並調用對應插件提供的addGlobalEventListener()方法
  addGlobalEventListener(target: string, eventName: string, 
    handler: Function): Function {
        const plugin = this._findPluginFor(eventName);
        return plugin.addGlobalEventListener(target, eventName, handler);
  }

  // 獲取NgZone
  getZone(): NgZone { return this._zone; }

  /** @internal */
  _findPluginFor(eventName: string): EventManagerPlugin {
    // 優先從_eventNameToPlugin對象中獲取eventName對應的EventManagerPlugin
    const plugin = this._eventNameToPlugin.get(eventName);  
    if (plugin) {
      return plugin;
    }

    // 遍歷插件列表,判斷當前插件是否支持eventName對應的事件名
    const plugins = this._plugins;
    for (let i = 0; i < plugins.length; i++) {
      const plugin = plugins[i];
      if (plugin.supports(eventName)) {
        this._eventNameToPlugin.set(eventName, plugin);
        return plugin;
      }
    }
    throw new Error(`No event manager plugin found for event ${eventName}`);
  }
}

相關說明

  • 在 addEventListener() 或 addGlobalEventListener() 方法內部都會調用_findPluginFor()方法,查詢對應的可以處理 eventName 對應的 EventManagerPlugin 插件對象。
  • _findPluginFor() 方法中,會遍歷插件列表,而後以eventName做爲參數調用插件對象提供的supports()方法,判斷當前是否可以處理eventName對應的事件。所以對於 EventManagerPlugin 插件對象,若是要聲明可以處理某類事件,就須要在supports()方法中進行相應處理。
  • DomEventsPlugin 插件做爲列表最後一項,由於它可以處理全部的事件。
  • KeyEventsPlugin、HammerGesturesPlugin、DomEventsPlugin 插件類都繼承於 EventManagerPlugin 抽象類。

EventManagerPlugin 抽象類

export abstract class EventManagerPlugin {
  constructor(private _doc: any) {}

  manager: EventManager;

  // 判斷是否支持eventName對應的事件
  abstract supports(eventName: string): boolean;

  // 添加事件監聽
  abstract addEventListener(element: HTMLElement, eventName: string, 
    handler: Function): Function;

  // 添加全局的事件監聽
  addGlobalEventListener(element: string, eventName: string, 
    handler: Function): Function {
      const target: HTMLElement = getDOM().getGlobalEventTarget(this._doc, element);
       if (!target) {
           throw new Error(`Unsupported event target ${target} for event 
            ${eventName}`);
       }
       return this.addEventListener(target, eventName, handler);
  };
}

圖片描述

用一個流程圖表示

GH9UMT.jpg

自定義事件處理

瞭解了事件的處理過程,是否疑惑咱們是否能夠自定義事件處理過程?是的咱們能夠,經過自定義EventManagerPlugin的方式,固然這應該是至關高端的操做了,有興趣的能夠看這篇文章

參考文章

javascript event(事件對象)詳解
Angular 4.x EventManager & Custom EventManagerPlugin
Angular 4.x Events Bubbling

相關文章
相關標籤/搜索