常见事件与DOM事件模型

1,477 阅读5分钟

常见事件

1.鼠标事件

  • 点击事件: click(单击),dblclick(双击),oncontentmenu (右键单击)
  • 按下抬起事件:mousedown(按下),mouseup(抬起)
  • 滚动滚轮事件:mousewheel(滚动)
  • 光标经过触发事件:mouseover(滑过),mouseout(滑出)
  • 光标移动时触发事件:mousemove(移动)
  • 光标进入和光标离开触发事件:mouseenter(进入), mouseleave(离开)

根据实际情况使用 mouseover 或 mouseenter

问题: 细心的读者可能已经发现了,mouseover和mouseout这组事件与mouseenter和mouseleave这组事件看起来很像,功能也很类似,那么为什么会有这两组事件呢,其中一种事件不就可以满足我们的需求了吗?
答案: 因为 mouseover 存在冒泡机制,会把自身的事件传递给祖先元素,事件会在当前元素和祖先元素上触发
而 mouseenter 不存在冒泡机制,只会在当前元素触发,祖先元素不会触发。(如果你还不了解什么是冒泡,可以先去看下面的事件冒泡,看完以后再回来看这里。)
所以,两种事件本质上是不同的,功能上是互补的。
总结: 你只需要判断是否需要在祖先元素上触发事件即可,如果需要则使用mouseover/mouseout,如果不需要则使用mouseenter/mouseleave。大多数情况下我们会使用mouseenter/mouseleave

2.键盘事件

  • keydown 按下任意按键时触发
  • keypress 除 Shift、Fn、CapsLock 外的任意键被按住时触发。(连续触发)
  • keyup 松开任意按键时触发

3.表单事件

  • onfocus 获取焦点
  • onblur 失去焦点
  • oninput 内容改变时触发
  • onchange 内容改变并且输入框失去焦点时触发

4.移动端Touch事件

  • touchstart 接触设备时触发
  • touchmove 在设备移动时触发
  • touchend 离开设备时触发
  • touchcancel 操作取消(非正常状态下操作结束)

5.系统事件

  • scroll 滚动
  • resize 大小改变时触发
  • load 加载完毕
  • beforeunload 即将被卸载
  • unload 正在被卸载

自定义事件

原生事件几乎已经可以满足我们的所有需求,但是除了原生事件外,我们还可以通过自定义事件来开发自己的事件系统。

    btn.addEventListener('click', ()=>{
        const event = new CostomEvent('myEvent', {
            detail: {name:'酸菜鱼',age: 24},
            bubbles: true,  // 事件会自动冒泡
            cancelable: false  // 不允许阻止冒泡
        )
        btn.dispatchEvent(event)
    })

然后像使用其它事件那样为自定义事件添加监听即可

    div1.addEventListener('myEvent', e => {
        console.log('我是自定义事件')
    })

如果你还想了解更多的事件行为,可以阅读MDN的事件参考文档。

DOM事件模型

DOM事件模型通常可以分为两个阶段,冒泡阶段和捕获阶段。

事件捕获和事件冒泡

事件捕获:从外部元素向内部元素逐级查找绑定监听函数的元素

事件冒泡:从内部元素向外部元素逐级查找绑定监听函数的元素

捕获不能取消,但冒泡可以取消

e.stopPropagation 取消冒泡

阻止默认行为 preventDefault

滚动事件无法通过e.stopPropagation取消冒泡

滚动事件取消冒泡

阻止滚轮事件

xxx.addEventListener('wheel',(e)=>{ e.preventDefault()}) 隐藏滚动条

::webkit-scrollbar{ width: 0 !important } 移动端取消触屏事件

xxx.addEventListener('touchstart', (e) => { e.preventDefault()})

事件绑定

默认DOM元素的点击事件为null addEventListener(IE8及以下不支持) onclick attachEvent

DOM 0级事件

<div id="btn"></div>
let btn = document.querySelector("btn")
btn.onclick =fcuntion(){}

缺点: 在一个事件上无法同时绑定多个处理函数(使用这种方式后续绑定的处理函数会覆盖之前的处理函数,所以只支持绑定单个处理函数)

DOM 2级事件

DOM 2级事件修复了一个处理程序无法同时绑定多个处理函数的缺点,可以控制函数的触发阶段(捕获或冒泡),对任何 DOM 元素都是有效的,而不仅仅只对 HTML 元素有效。

<div id="btn"></div>
let btn = document.querySelector("btn")
btn.addEventListener = ('click', function(){}, false)
btn.addEventListener = ('click', function(){}, false)

e只存在于事件触发的时候,时间结束后,e就不存在了

addEventListener(事件名,回调函数,布尔值),通常我们在使用addEventListener的时候只会传递前两个参数,而不会传递第三个参数,第三个参数就是来决定把事件添加到冒泡阶段还是捕获阶段的,如果为true,函数在捕获阶段执行,如果不传或者为falsy值,函数则会在冒泡阶段执行。

removeEventListener 移除事件绑定

// IE8级以下版本只支持冒泡型事件,不支持事件捕获所以没有第三个参数
// 方法中包含2个参数,分别是绑定的事件处理属性名称(不包含on)、事件处理函数
btn.attachEvent('onclick', fn); // 绑定事件 
btn.detachEvent('onclick', fn); // 解绑事件 

DOM 3级事件

添加了更多地事件类型

事件对象

  1. 鼠标事件对象
  2. 键盘事件对象
  3. Touch事件对象

如果你想在事件结束后使用当时触发事件的e对象,请把它保存起来

e.target vs e.currentTarget

target用户点击的,currentTarget 开发者监听的

不过有一种特殊情况,只在一个DOM元素的捕获和冒泡阶段监听函数,谁先监听谁先执行

事件的特性

  • Bubbles 表示是否冒泡
  • Cancelable 表示是否支持开发者取消冒泡
  • 捕获不能取消,但冒泡可以取消
  • e.stopPropagation 取消冒泡

事件委托

事件委托又叫事件代理,是指不监听元素本身,而去监听其祖先元素,然后判断触发事件的元素是否为该元素
应用场景:有多个子元素需要同时绑定监听事件,并且需要在新增兄弟元素后也自动为它绑定同样的监听事件,此时就需要使用事件委托

优点:

1.减少监听事件的数量   
2.动态添加的元素也可以被监听到

自己封装一个事件委托函数

on('click','#div2','button',fn)

function on(eventType, element, selector, fn){
    if(!(element instanceof Element)){
        element = document.querySelector(element)
    }
    element.addEventListener(eventType, e => {
        let el = e.target
       while(!(el.matches(selector))){
            if(element === selector){
                el = null
                break
            }
            el = el.parentNode
        }
        el && el.call(el, e, el)
   })
}

最后提出一个拓展问题:既然存在自定义事件的方法,那么我们就有了实现JS事件系统的的基础,大家可以思考一下如何手写一个JS事件系统(提示:使用队列)