DOM面试题-DOM事件流、事件模型

面试题:你知道DOM事件有哪些级别吗? 

 

面试题:描述一下DOM事件模型和DOM事件流?

面试题:描述一下DOM事件捕获的具体流程?

面试题:Event对象有哪些常见应用?你知道什么是自定义事件吗?

一、DOM事件级别

DOM一共分为4个级别:DOM0级,DOM1级,DOM2和DOM3级。

DOM事件一共分为3个级别:DOM0级事件处理,DOM2级事件处理,DOM3级事件处理。

由于DOM1级并没有规定事件相关内容,所以没有DOM1级事件。

1、DOM0级事件

 el.οnclick=function(){}

如果给同一个元素/标签绑定多个同类型事件,只有最后一次会被执行。

  <button id="btn">Click Me!</button>
  <script>
    const btn = document.getElementById('btn')
    btn.onclick = function(){
      alert("第一个事件处理函数")
    }
    btn.onclick = function(){
      alert("第二个事件处理函数")
    }
    btn.onclick = function(){
      alert("第三个事件处理函数")
    }
  </script>

2、DOM2级事件

el.addEventListener(event-name,callback,useCapture)

  • event-name:事件名称,可以是标准DOM事件
  • callback:回调函数,当事件触发时,函数会被注入一个参数:但概念的事件对象event
  • useCapture:默认是false,代表事件句柄在冒泡阶段执行。ture则为捕获阶段。

通过el.addEventListener添加的事件只能通过el.removeEventListener来移除,传入的参数与addEventListener相同。

el.addEventListener添加的事件时,如果传入的是匿名函数,那么将无法移除事件监听。因为两个匿名函数的内存地址是不相同的,故推荐传入具名函数。

通过el.addEventListener可以为同一元素添加多个事件监听。

(如以下代码,事件1和事件2均会执行,事件3被移除了)

 <button id="btn">Click Me!</button>
  <script>
    const btn = document.getElementById('btn')
    btn.addEventListener('click',test1)
    btn.addEventListener('click',test2)
    btn.addEventListener('click',test3)
    function test1(e){
      console.log(e)
      alert('第一个事件')
    }
    function test2(e){
      console.log(e)
      alert('第二个事件')
    }
    function test3(){
      console.log(e)
      alert('第三个事件')
    }
    btn.removeEventListener('click',test3)
  </script>

3、DOM3级事件

在DOM2级事件的基础上添加了更多的事件类型。

  • UI事件:当用户与页面上的元素交互时触发,如load,scroll
  • 焦点事件:当元素获得或失去焦点时触发,如focus,blur
  • 鼠标事件:当用户通过鼠标在页面执行操作时触发,如click/dblclick,mousedown/mouseup,mouseout/mouseover
  • 滚轮事件:当使用鼠标滚轮时触发,如mousewheel
  • 键盘事件:当用户通过键盘触发页面操作时,如keyup,keydown
  • 合成事件:当为输入法编辑器输入字符时触发,compositionstart
  • 变动事件:当底层DOM结构发生变化时触发,如DOMsubtreeModified
  • DOM3级事件允许使用者自定义一些事件
     

二、DOM事件模型和事件流

DOM事件模型分为捕获和冒泡。

事件流:一个事件发生之后,会在子元素和父元素之间传播。这种传播分为3个阶段。

(1)捕获阶段:事件从window对象自上而下向目标节点传播的阶段

(2)目标阶段:真正的目标节点正在处理事件的阶段。

(3)冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。

三、DOM事件捕获流程

捕获过程:(自上而下)window→document→html→body→...普通html结构..→目标元素

而冒泡过程则是捕获过程的逆过程。

<div id="outer">
   <div id="inner">inner</div>
 </div>
 <script>
   window.onclick=function(){
     console.log('window');
   }
   document.onclick=function(){
     console.log('document');
   }
   document.documentElement.onclick=function(){
     console.log('html');
   }
   document.body.onclick=function(){
     console.log('body')
   }
   const outer = document.getElementById('outer')
   const inner = document.getElementById('inner')
   outer.onclick=function(){
     console.log('out')
   }
   inner.onclick=function(){
     console.log('inner')
   }
 </script>

 输出结果: inner → out → body → html → document → window

 绑定的事件处理函数是在当前元素事件行为的冒泡阶段(或目标阶段)执行的。

四、事件代理(事件委托)

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数绑定在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫事件代理(事件委托)。

面试题:有一个id为list的列表,要求:往其中插入100个节点,点击每个列表项时,能弹出是第几个。

<ul id="list"></ul>
<script>
  (()=>{
    const container = document.getElementById('list')
    // 创建文档片段,此时还没有插入到DOM树中
    const fragments = document.createDocumentFragment()
    // 动态创建节点并组合到文档片段中
    for(let i = 0; i < 100; i++){
      const li = document.createElement("li")
      li.innerHTML = `item-${i+1}`
      fragments.appendChild(li)
    }
    // 一次性插入DOM树中
    container.appendChild(fragments)
    container.addEventListener('click',function(e){
      if(e.target.tagName === 'LI'){
        alert(e.target.innerHTML)
      }
    })
  })()
</script>

追问:那如果是插入50000个节点呢?

<ul id="list"></ul>
<script>
  (()=>{
    const container = document.getElementById('list')
    const TOTAL = 50000
    const BATCH_SIZE = 10 // 每批插入节点次数,越大越卡
    const BATCH_COUNT = TOTAL / BATCH_SIZE // 需要批处理多少次
    let batchDone = 0 // 已经完成的批处理个数

    function appendItems(){
      const fragments = document.createDocumentFragment()
      for(let i = 0; i < BATCH_SIZE; i++){
        const item = document.createElement("li")
        item.innerHTML = `item-${(batchDone * BATCH_SIZE ) + i + 1}` 
        fragments.appendChild(item)
      }
      container.appendChild(fragments)
      batchDone += 1
      batchAppend()
    }

    function batchAppend(){
      if(batchDone < BATCH_COUNT){
        window.requestAnimationFrame(appendItems)
      }
    }

    batchAppend()

    container.addEventListener('click',function(e){
      if(e.target.tagName === 'LI'){
        alert(e.target.innerHTML)
      }
    })
  })()
</script>

五、Event对象常见应用

1、event.preventDefault() 

阻止默认行为。

例如表单点击提交按钮跳转页面,a标签默认页面跳转或者锚点定位

如果使用a标签当做普通按钮,实现点击功能,既不跳转页面,也不锚点定位:

① href="javascript:;"

 <a href="javascript:;">Click</a>

②return false 

 <a href="http://www.baidu.com">Click</a>
 <script>
   const a = document.getElementsByTagName("a")[0]
   a.onclick=function(e){
     return false
   }
 </script>

③e.preventDefault()

 <a href="http://www.baidu.com">Click</a>
 <script>
   const a = document.getElementsByTagName("a")[0]
   a.onclick=function(e){
     e.preventDefault()
   }
 </script>

2、event.stopPropagation()

阻止事件冒泡到父元素,阻止任何父元素处理程序被执行

以下代码仅输出inner

 <div id="outer">
   <div id="inner">inner</div>
 </div>
 <script>
   window.onclick=function(){
     console.log('window');
   }
   document.onclick=function(){
     console.log('document');
   }
   document.documentElement.onclick=function(){
     console.log('html');
   }
   document.body.onclick=function(){
     console.log('body')
   }
   const outer = document.getElementById('outer')
   const inner = document.getElementById('inner')
   outer.onclick=function(){
     console.log('out')
   }
   inner.onclick=function(e){
     console.log('inner')
     e.stopPropagation()
   }
 </script>

3、event.stopImmediatePropagation()

既能阻止事件向父元素冒泡,也能阻止元素同类型事件的其它监听被触发。而stopPropagation只能实现前者的效果。

如下代码,id为inner的元素绑定了2个事件处理函数,在第一个事件处理函数中,如果只写了e.stopPropagation(),将阻止事件向父元素冒泡,但事件2的处理函数仍然会执行。

但如果换成e.stopImmediatePropagation(),则不仅阻止事件向父元素冒泡,事件2的处理函数也不执行。

 <div id="outer">
   <div id="inner">inner</div>
 </div>
 <script>
   window.onclick=function(){
     console.log('window');
   }
   document.onclick=function(){
     console.log('document');
   }
   document.documentElement.onclick=function(){
     console.log('html');
   }
   document.body.onclick=function(){
     console.log('body')
   }
   const outer = document.getElementById('outer')
   const inner = document.getElementById('inner')
   outer.onclick=function(){
     console.log('out')
   }
   inner.addEventListener('click',function(e){
     console.log('inner -- 事件1');
    //  e.stopPropagation()
    e.stopImmediatePropagation()
   })
   inner.addEventListener('click',function(e){
     console.log('inner -- 事件2');
   })
 </script>

4、event.currentTarget  & event.target

event.target是事件触发的元素,event.currentTarget是当事件沿着DOM触发时事件的当前目标,它总是指向事件绑定的元素。

如下代码,当点击id4的div时,控制台依次输出:

e.target:id4,e.currentTarget:id4
e.target:id4,e.currentTarget:id3
e.target:id4,e.currentTarget:id2
e.target:id4,e.currentTarget:id1

 <div id="id1">
   <div id="id2">
    <div id="id3">
      <div id="id4"></div>
    </div>
   </div>
 </div>
 <script>
   const box1 = document.getElementById('id1')
   const box2 = document.getElementById('id2')
   const box3 = document.getElementById('id3')
   const box4 = document.getElementById('id4')
   box1.addEventListener('click',function(e){
     console.log(`e.target:${e.target.id},e.currentTarget:${e.currentTarget.id}`)
   })
   box2.addEventListener('click',function(e){
     console.log(`e.target:${e.target.id},e.currentTarget:${e.currentTarget.id}`)
   })
   box3.addEventListener('click',function(e){
     console.log(`e.target:${e.target.id},e.currentTarget:${e.currentTarget.id}`)
   })
   box4.addEventListener('click',function(e){
     console.log(`e.target:${e.target.id},e.currentTarget:${e.currentTarget.id}`)
   })
 </script>

六、自定义事件

Event 与 CustomEvent

window.addEventListener() 添加事件监听

window.dispatchEvent() 抛出事件

Event算是一个顶级接口,CustomEvent基于Event,增加了部分参数

 setTimeout(() => {
    // 自定义事件
  const test = new Event('test')
  test.app = '额外参数'

  const result = window.dispatchEvent(test)
  console.log(result); // 返回的是一个布尔值
 
 }, 3000);
 function testHandler(params) {
    console.log('自定义事件',params) // params是一个event对象
  }
  window.addEventListener('test',testHandler)
 setTimeout(() => {
    // 第二个参数可以直接传入参数
    const customEvent = new CustomEvent('customEvent',{
      detail:{
        name:"eric",
        height:180,
        weight:120
      },
      bubbles:true, // 表示事件是否可以冒泡,默认不冒泡
      cancelable:false // 表示该事件是否可以取消
    })
    // 也可以直接在事件对象上绑定参数
    customEvent.app = 'customEvent的额外参数'
    window.dispatchEvent(customEvent)
  }, 3000);
  function customEventHandler(params) {
    console.log("customEvent",params) // params是一个CustomEvent对象
  }
  window.addEventListener('customEvent',customEventHandler)

  • 22
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值