以前对事件都是能用就行,本周对事件进行了一下较全面的学习, 在这里记录一下。
什么是事件
事件可以理解为行为,如对button的点击,事件的本质是一个函数,他接收一个event对象。
每个事件会产生一个Event对象
,Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。
事件传播机制
事件的传播机制可以分为冒泡
和捕获
冒泡
官方的定义就是从最特定的事件目标到最不特定的事件目标。
意思就是说,假如用户单击了一个元素,该元素拥有一个click事件,那么同样的事件也将会被它的祖先触发,这个事件从该元素开始一直冒泡到DOM树的最上层,这一过程称为事件冒泡
捕获
事件捕获和事件是相反的,也就是说,当用户触发了一个事件的时候,这个事件是从DOM树的最上层开始触发一直到捕获到事件源。
事件流
事件流是事件传播机制的标准
由于微软和网景乱搞,后来必须要为事件传播机制,制定一个标准,因为事件捕获是网景公司开发出来的,而事件冒泡是由微软公司开发出来的,它们都想要自己的技术成为标准,所以导致这两个公司老是干架,制定标准的人为了不让它们干架,所以研发了事件流。
事件流的使用
事件流的使用就是我们常用的addEventListener
了,标准用法如下
dom对象.addEventListener(事件类型, 回调函数, 事件机制)
这里的事件类型表示你要使用哪种事件类型比如click
, 回调函数里面写着触发此事件你要做什么事情, 事件机制分为冒泡和捕获,如果为false
表示事件冒泡,为true
表示事件捕获
发现自己以前一直使用的都是不标准的写法
dom对象.attachEvent(eventType, fn)
第一个参数表示事件类型,第二个是回调,这种的事件传播机制是冒泡
关于js的事件就到这里了,如果想要了解js的兼容写法等可以查看这篇文章。
angular事件
Angular 组件和 DOM 元素通过事件与外部进行通信, Angular 事件绑定语法对于组件和 DOM 元素来说是相同的 - (eventName)="expression" :
<button (click)="onClick()">Click</button>
angular 事件特点:
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);
};
}
用一个流程图表示
自定义事件处理
了解了事件的处理过程,是否疑惑我们是否可以自定义事件处理过程?是的我们可以,通过自定义EventManagerPlugin的方式,当然这应该是相当高端的操作了,有兴趣的可以看这篇文章
参考文章
javascript event(事件对象)详解
Angular 4.x EventManager & Custom EventManagerPlugin
Angular 4.x Events Bubbling
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。