html和js是通过事件机制进行交互的,事件主要包括事件流,事件模型,事件委托,事件循环
事件流
事件流就是页面中处理事件的顺序
- 分为三个阶段 捕获阶段->目标阶段->冒泡阶段 依次调用对应阶段的处理函数
捕获阶段:事件从window到目标自上而下的传播阶段目标阶段:处理目标事件的阶段冒泡阶段:事件从目标节点到window自下而上的传播阶段
- 由于历史原因分为两个事件模型 IE为冒泡流 netscape为捕获流 w3c折中采取先捕获后冒泡流(可以通过addEventListener第三个参数设置)
DOM事件级别
DOM级别有4个 DOM0 DOM1 DOM2 DOM3
DOM事件级别有3个 DOM0 DOM2 DOM3
DOM0 (处于冒泡/目标阶段)
- el.onclick=function(){}
var btn = document.getElementById('btn');
btn.onclick = function(){
alert(this.innerHTML);
}
DOM2 级事件
- el.addEventListener(eventName, callback, useCapture)
- eventName: 事件名称,可以是标准的DOM事件
- callback: 回调函数,参数为当前的事件对象 event
- useCapture: 默认是false(冒泡阶段执行) ,true捕获阶段执行
var btn = document.getElementById('btn');
btn.addEventListener("click", test, false);
function test(e){
e = e || window.event;
alert((e.target || e.srcElement).innerHTML);
btn.removeEventListener("click", test)
}
//IE9-:attachEvent()与detachEvent()。
//IE9+/chrom/FF:addEventListener()和removeEventListener()
IE9以下的IE浏览器不支持 addEventListener()和removeEventListener(),使用 attachEvent()与detachEvent() 代替
DOM3 级事件
- UI事件,当用户与页面上的元素交互时触发,如:load、scroll
- 焦点事件,当元素获得或失去焦点时触发,如:blur、focus
- 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dblclick、mouseup
- 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
- 文本事件,当在文档中输入文本时触发,如:textInput
- 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
- 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
- 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified
- 同时DOM3级事件也允许使用者自定义一些事件。
总结
- DOM2级的好处是可以添加多个事件处理程序;DOM0对每个事件只支持一个事件处理程序;
- 通过DOM2添加的匿名函数无法移除,addEventListener和removeEventListener的handler必须同名
- 作用域:DOM0的handler会在所属元素的作用域内运行,IE的handler会在全局作用域运行,this === window
- 触发顺序:添加多个事件时,DOM2会按照添加顺序执行,IE会以相反的顺序执行
兼容ie9的DOM0
var EventUtil = {
// element是当前元素,可以通过getElementById(id)获取
// type 是事件类型,一般是click ,也有可能是鼠标、焦点、滚轮事件等等
// handle 事件处理函数
addHandler: (element, type, handler) => {
// 先检测是否存在DOM2级方法,再检测IE的方法,最后是DOM0级方法(一般不会到这)
if (element.addEventListener) {
// 第三个参数false表示冒泡阶段
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent(`on${type}`, handler)
} else {
element[`on${type}`] = handler;
}
},
removeHandler: (element, type, handler) => {
if (element.removeEventListener) {
// 第三个参数false表示冒泡阶段
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent(`on${type}`, handler)
} else {
element[`on${type}`] = null;
}
}
}
// 获取元素
var btn = document.getElementById('btn');
// 定义handler
var handler = function(e) {
console.log('我被点击了');
}
// 监听事件
EventUtil.addHandler(btn, 'click', handler);
// 移除事件监听
// EventUtil.removeHandler(button1, 'click', clickEvent);
事件委托/事件代理
基于事件流的传播特性,可以在目标节点的父节点上定义监听函数,由父节点统一处理多个子节点的事件,被称为事件代理
事件代理优点
- 减少内存消耗,提升性能 假如子元素数量特别多,每个都需要绑定事件就会消耗大量内存,造成性能上的浪费
- 可以动态绑定 直接给所有子元素绑定事件,新增子元素时需要重新绑定事件,销毁子元素需要重新解绑,使用事件代理可以解决这个麻烦
事件对象Event
api
- event.preventDefault() 阻止默认事件触发
- event.stopPropagation() 阻止事件冒泡(阻止父事件执行)
- event.stopImmediatePropagation() 既能阻止事件冒泡也能阻止同事件的其它监听函数触发
- event.target 触发事件的目标元素
- event.currentTarget 绑定事件的元素
事件循环EventLoop
- 事件循环由执行栈和任务队列构成
- 所有的同步代码都会放到执行栈中去执行,执行的顺序是后进先出
- 因为js是单线程,为了不阻塞主线程,当遇到异步任务时会将其放到任务队列中去等待执行 任务队列又分为宏任务和微任务
宏任务macro-task/task
包括:
- script(整体代码)
- setTimeout/setInterval
- setImmediate
- I/O
- UI render
微任务micro-task/jobs
包括:
- process.nextTick
- Promise
- Async/Await(实际就是promise)
- MutationObserver(html5新特性)
具体执行过程
- 首先执行宏任务中的代码,会同步执行代码,
- 当遇到宏任务时会将这个宏任务的处理函数放到宏任务队列
- 当遇到微任务将微任务的处理函数放到微任务队列
- 当执行完当前宏任务代码时,先检查微任务队列,如果存在微任务就执行微任务,清空微任务队列后再执行宏任务队列中的下一个宏任务,形成事件循环