javascript中的事件

195 阅读7分钟

1.什么是事件 ?

事件是浏览器赋予元素的默认行为,也可以理解为事件是天生具备的,不论我们是否为其绑定方法,当某些行为触发的时候,相关的事件都会被触发执行!

参考都有哪些事件 developer.mozilla.org/zh-CN/docs/…

  • 鼠标事件

    • click 点击事件(PC:频繁点击N次,触发N次点击事件) 单击事件(移动端:300ms内没有发生第二次点击操作,算作单击事件行为,所以click在移动端会有300ms延迟)
    • dblclick 双击事件
    • contextmenu 鼠标右键点击触发
  • 键盘事件

    • keydown keypress keyup
  • 手指事件 (移动端)

    • touchstart
    • touchmove
    • touchend
    • touchcancel
    • gestureStart(多个手指)
  • 表单事件

  • 视图事件

    • resize
    • scroll
  • 资源事件

    • load 加载成功 window.onload img.onload
    • error 加载失败
    • beforeunload 资源卸载之前 window.beforeunload 页面关闭之前触发
  • ...

2.什么是事件绑定?

给元素默认的事件行为绑定方法,这样可以在行为触发的时候,执行这个方法。

  • DOM0级事件绑定

    docoment.body.onclick = function(){} // 绑定
    docoment.body.onclick = null // 删除
    

    原理:每一个DOM元素对象的私有属性上都有很多类似于 onxxx 的私有属性,我们给这些代表事件的私有属性赋值,就是DOM0事件绑定

    • 如果没有对应事件的私有属性值,例如DOMContentLoaded, 则无法基于这种方法实现事件绑定
    • 只能给当前元素的某个事件行为绑定一个方法,绑定多个方法,最后一个操作会覆盖以往的
    • 好处是执行效率快,而且开发者使用起来方便。

    DOM0级事件绑定中给元素事件行为绑定的方法,都是在目标阶段/冒泡阶段触发的。(参考下面的事件传播机制)

  • DOM2级事件绑定

    document.body.addEventListener('click',fn1,[冒泡/捕获]==[false/true])
    //(最后一个参数默认是false,控制方法是在冒泡阶段触发执行,如果设置为true可以控制在捕获阶段触发执行)
    document.body.removeEventListener('click',fn1,[冒泡/捕获])  //删除绑定的方法,后面参数和绑定的时候一样才可以
    

    原理:每一个DOM元素都会基于原型链的查找机制,找到EventTarget.prototype的addEventListener和removeEventListener等方法,基于这些方法实现事件的绑定和移除,DOM2级事件绑定采用事件池机制。放到EventQueue 事件队列中。有元素,事件类型,方法,阶段(捕获,冒泡)属性,所以多个方法可以依次注入到事件池里面。通过addEventListener注入,removeEventListener移除。是一种发布订阅模式。

    • DOM2级事件绑定,绑定的方法一般不是匿名函数。匿名函数的话,删除不能删。
    • 凡是浏览器提供的事件行为,都可以基于这种模式完成事件的绑定和移除。(DOM0中的window.onDOMContentLoaded是不行的,因为没有这个私有属性)但是我们可以 window.addEventListener('DOMContentLoaded',func) 这样是可以的
    • 可以给当前元素的某个事件类型绑定多个 不同 的方法(进入到事件池)。这样事件触发,会从事件池中依次(按照绑定顺序)取出方法执行。

    DOM2级事件绑定可以控制绑定的方法在捕获阶段触发(虽然没有实际意义)

3.事件对象

存储当前事件操作及触发的相关信息的(浏览器本身记录的,记录的是当前这次操作的信息,和在哪个函数中无关)给当前元素的某个事件行为绑定方法,当事件行为触发,不仅会把绑定的方法执行,而且还会给方法默认传递一个实参,而这个实参就是我们的事件对象

  • 鼠标事件对象 MouseEvent

    • clientX/clientY 鼠标触发点距离当前窗口的X/Y轴坐标
    • pageX/pageY 鼠标触发点距离Body的
    • type 事件类型
    • path 传播路径
    • target/srcElement 获取当前的事件源(当前操作的元素)
    • ev.preventDefault()/ ev.returnValue = false 阻止默认行为 这种两种表示一个意思的 都是为了适配兼容的写法
    • ev.stopPropagation()/ ev.cancelBubble = true 阻止冒泡的
  • 键盘事件对象 KeyboardEvent  ------------------------例如keydown

    • which/keyCode 获取按键的键盘码(window键盘)
      • 方向键 左37 上38 右39 下40
      • Space 32
      • BackSpace 8
      • Del 46
      • Enter 13
    • altkey 是否按了alt键
    • ctrlKey
    • shiftKey
    • key/code 存储按键名字,但是这个可能有点不准
    • 同上的阻止默认行为和冒泡传播的都有
  • 手指事件对象 TouchEvent (移动端)

    • changedTouches/targetTouches/touches 记录手指信息的,平时常用changedTouches 手指按下,移动,离开屏幕 都存储了对应的信息,哪怕离开屏幕后,存储的也是最后一次手指在屏幕中的信息。=> 获取的结果都是一个TouchList集合,记录每一根手指的信息。例如 ev.changedTouches[0] 里面有 clientY pageY 等信息。

    • 事件对象 Event

4.阻止默认行为

浏览器会赋予元素很多的默认行为操作

  • 鼠标右键菜单
  • 点击a标签跳转页面
  • 部分浏览器会记录输入记录,下次再输入的时候 有模糊匹配
  • 键盘按下会输入内容
  • ...

我们可以基于ev.preventDefault() 来禁用这些默认行为

A标签的默认行为: href="javascript:;" 阻止锚点定位。

5.事件的传播机制

  // 如下HTML
  <div class="box">
       <div class="outer">
           <div class="inner">
               
           </div>
       </div>
   </div> 
 // 层级关系为
 window->
 document->
 html->
 body->
 box->
 outer->
 inner->  (点击)
  • 阶段1:捕获阶段->从外往里找 从window 一直找到inner。

    目的是为冒泡阶段的传播提供路径。ev.path就是存放捕获阶段的收集的传播路径

  • 阶段2:目标阶段->触发事件源的相关事件行为

    把当前事件源的相关事件行为触发

  • 阶段3:冒泡阶段-> 由内向外

    按照捕获阶段收集的传播路径,不仅仅当前事件源的相关事件行为被触发,而且从内到外,其祖先所有元素的相关事件行为也都会被触发(如果做了事件绑定,绑定的方法也会执行)

    如上:inner的click行为触发,outer也会触发 一层层 box body 一直到最上面,事件都会被触发,如果绑定了方法,都会执行。

    DOM0级事件绑定中给元素事件行为绑定的方法,都是在目标阶段/冒泡阶段触发的。

    DOM2级事件绑定可以控制绑定的方法在捕获阶段触发(虽然没有实际意义) ;addEventListener第三个参数为true的时候 是在捕获阶段触发。

应用:

  • 1.阻止冒泡

    ev.stopPropagation?ev.stopPropagation():ev.cancelBubble=true
    ev.cancelBubble=true //兼容IE6~8的写法。
    
  • 2.事件委托/事件代理

    利用事件的冒泡传播机制(核心和前提),我们可以把一个容器A中,所有后代元素的某个事件行为触发操作,委托给当前容器A的某个事件行为。因为点击事件行为,存在冒泡传播机制,所以不论点击inner outer 还是box 最后都会传播到body上。触发body的事件行为,把为其绑定的方法执行。

    优点:

    • 性能高 60%左右

    • 可以操作动态绑定的元素

    • 某些需求必须基于它完成

      // 基于委托判断 点击的是inner还是某个盒子 然后执行不同的代码
      document.body.onclick= function(ev){
         let target = ev.target
         let targetClass = target.className
         if(targetClass === 'inner'){
             console.log('inner')
             return
         }
         if(targetClass === 'outer'){
             console.log('outer')
             return
         }
         if(targetClass === 'box'){
             console.log('box')
             return
         } 
      }
      

6.mouseover 和 mouseenter区别

  • mouseover 和 mouseout 存在事件的冒泡传播机制 会触发多次
  • mouseenter 和 mouseleave 默认阻止了事件的冒泡传播机制 这个是我们需要的。不会触发多次

项目中,如果我们做 鼠标划入划出操作 有子元素的话 一般用mouseenter

案例

1.查看图片放大镜功能