浏览器&网络(浏览器事件)

131 阅读4分钟

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新特性)

具体执行过程

  • 首先执行宏任务中的代码,会同步执行代码,
  • 当遇到宏任务时会将这个宏任务的处理函数放到宏任务队列
  • 当遇到微任务将微任务的处理函数放到微任务队列
  • 当执行完当前宏任务代码时,先检查微任务队列,如果存在微任务就执行微任务,清空微任务队列后再执行宏任务队列中的下一个宏任务,形成事件循环

image.png