事件级别
- DOM 0 级 - el.onclick = function () {};- 当希望为同一个元素/标签绑定多个同类型的事件时(如给同一个按钮绑定三个点击事件),是不被允许的。DOM 0 事件绑定,给元素的事件行为绑定方法,这些方法都是在当前元素事件行为的冒泡阶段(或者目标阶段)执行的 
- DOM 2 级 - el.addEventListener(eventName, callback, useCapture);
 // eventName: 事件名称,可以是标准的 DOM 事件
 // callback: 回调函数,当事件触发时,函数会被注入一个参数为当前的事件对象 event
 // useCapture: 默认为false,代表事件句柄在冒泡阶段执行
- DOM 3 级,写法和 DOM 2 级一致,只是在 DOM 2 级事件的基础上添加了更多的事件类型 - UI 事件:当前用户与页面的元素交互时触发:load,scroll - 焦点事件:当元素获得或失去焦点时触发:blur,focus - 鼠标事件:当用户通过鼠标在页面执行操作时触发:dblclick,mouseup - 滚轮事件:当使用鼠标滚轮或类似设备时触发:mousewheel - 文本事件:当在文档中输入文本时触发:textinput - 键盘事件:当用户通过键盘在页面上执行操作时触发:keydown,keypress - 合成事件:当为 IME(输入法编辑器)输入字符时触发:compositionstart - 变动事件:当底层 DOM 结构发生变化时触发:DOMsubtreeModified - 同时 DOM 3 级事件也允许使用者自定义一些事件 
DOM 事件模型 事件流
事件模型分为:捕获和冒泡
事件流:
- 捕获阶段:事件从 window 对象自上而下向目标节点传播的阶段
- 目标阶段:真正的目标节点正在处理事件的阶段
- 冒泡阶段:事件从目标节点自下而上向 window 对象传播的阶段
事件委托(代理)
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫事件的代理。
优点:
- 减少内存消耗,不需要为每个子元素绑定事件,提高性能
- 动态绑定事件
Event 对象使用
- 阻止默认行为: - event.preventDefault()
- 阻止冒泡: - event.stopPropagation()阻止事件冒泡到父元素,阻止任何父事件处理程序被执行
- event.stopImmediatePropagation()既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其他监听器被触发
 
- event.target & event.currentTarget- <div id="a">
 aaaa
 <div id="b">
 bbbb
 <div id="c">
 cccc
 <div id="d">dddd</div>
 </div>
 </div>
 </div>
 <script>
 document.getElementById("a").addEventListener("click", function(e) {
 console.log(
 "target:" e.target.id + "& currentTarget:" + e.currentTarget.id
 )
 })
 document.getElementById("b").addEventListener("click", function(e) {
 console.log(
 "target:" e.target.id + "& currentTarget:" + e.currentTarget.id
 )
 })
 document.getElementById("c").addEventListener("click", function(e) {
 console.log(
 "target:" e.target.id + "& currentTarget:" + e.currentTarget.id
 )
 })
 document.getElementById("d").addEventListener("click", function(e) {
 console.log(
 "target:" e.target.id + "& currentTarget:" + e.currentTarget.id
 )
 })
 </script>
 <!--
 当我们点击最里层的元素 d 时,会依次输出:
 target: d & currentTarget: d
 target: d & currentTarget: c
 target: d & currentTarget: b
 target: d & currentTarget: a
 -->- 由上述例子可知: - event.currentTarget始终是监听事件者,而- event.target是事件的真正发出者
自定义事件
// 创建事件,Event 是无法传递参数的
var event = new Event("event");
// 创建参数,CustomEvent 允许传递参数
var event = new CustomEvent("event", { detail: "Hello world" });
// 监听事件
el.addEventListener(
    "event",
    function (e) {
        // ...
    },
    false
);
// 分发事件
el.dispatchEvent(event);
手写发布订阅模式
class EventEmitter {
    events: { [key: string]: Function[] } = {};
    // 订阅
    on(type: string, callback: Function) {
        if (!this.events) this.events = Object.create(null);
        if (!this.events[type]) {
            this.events[type] = [callback];
        } else {
            this.events[type].push(callback);
        }
    }
    // 取消订阅
    off(type: string) {
        if (!this.events[type]) return;
        delete this.events[type];
    }
    // 只执行一次订阅
    once(type: string, callback: Function) {
        function fn() {
            callback();
            this.off(type);
        }
        this.on(type, fn);
    }
    // 触发事件
    emit(type: string, ...rest) {
        this.events[type] && this.events[type].forEach((fn) => fn(...rest));
    }
}
// 使用情况
const event = new EventEmitter();
event.on("click", (...rest) => {
    console.log(rest);
});
event.emit("click");
event.off("click");
event.once("click", (...rest) => {
    console.log(rest);
});