DOM事件模型和事件流

1、什么是DOM事件流

事件流描述的是从页面中接收事件的顺序

事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流。

DOM事件流中有三个阶段:捕获阶段,当前目标阶段,冒泡阶段

2、DOM事件流中的三个阶段

  1. JS代码中只能执行捕获或者冒泡其中的一个阶段。

  1. onclick和 attachEvent (ie)只能得到冒泡阶段。

  1. addEventListener(type,listener[,useCapture])第三个参数如果是 true,表示在事件捕获阶段调用事件处理程序;如果是false (不写默认就是false ),表示在事件冒泡阶段调用事件处理程序。

  1. 实际开发中我们很少使用事件捕获,我们更关注事件冒泡

  1. 有些事件是没有冒泡的,比如onblur、onfocus、onmouseenter、onmouseleave

2.1、捕获阶段

如果addEventListener第三个参数是true,则处于捕获阶段

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

<body>
    <div class="father">
        <div class="son">son盒子</div>
    </div>
    <script>
        var son = document.querySelector('.son');
        son.addEventListener('click',function(){
            alert('son')
        },true);
        var father = document.querySelector('.father');
        father.addEventListener('click',function(){
            alert('father')
        },true);
    </script>
</body>
因为addEventListener的第三个参数为true,所以为捕获阶段。弹出的先后顺序是 father -> son

2.2、冒泡阶段

如果addEventListener第三个参数是false或者省略,则处于冒泡阶段

冒泡阶段(自下而上): son \ father \ body \ html \ document

<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>
1. JS代码只能执行捕获或冒泡其中一个阶段
2. onclick和attachEvent只能得到冒泡阶段
3. 如果addEventListener第三个参数为false或省略,处于冒泡阶段
以上代码点击inner盒子输出顺序: inner、out、body、html、document、window
绑定的事件处理函数是在当前元素事件行为的冒泡阶段(或目标阶段)执行的

3、阻止事件冒泡

3.1、标准写法

利用事件对象里面的stopPropagation()方法

<body>
    <div class="father">
        <div class="son">son盒子</div>
    </div>
    <script>
        //标准写法∶利用事件对象里面的stopPropagation()方法 
        //stop  停止Propagation  传播
        var son = document.querySelector('.son');
        son.addEventListener('click',function(e){
            alert('son')
            e.stopPropagation();
        },false);
        var father = document.querySelector('.father');
        father.addEventListener('click',function(){
            alert('father')
        },false); 
        document.addEventListener('click',function(){
            alert('document')
        },false); 
    </script>
</body>
只弹出了son ,没有father

3.2、兼容性写法

var son = document.querySelector('.son');
son.addEventListener('click',function(e){
    alert('son')
    if (e && e.stopPropagation) {
        e.stopPropagation();
    }else{
        window.event.cancelBubble = true;
    }    
},false);

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

事件委托,也成事件代理,在jQuery中也叫事件委派

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

案例:给ul注册点击事件,然后利用事件对象的target来找到当前点击的li,因为点击li,事件会冒泡到ul上,ul有注册事件,就会触发事件监听器

<body>
    <ul>
        <li>hello world</li>
        <li>hello world</li>
        <li>hello world</li>
        <li>hello world</li>
        <li>hello world</li>
    </ul>
    <script>
        //给父节点添加监听器,然后利用冒泡原理影响设置每个子节点。 
        var ul = document.querySelector('ul');
        ul.addEventListener('click',function(e){
            alert('hello world');
            //e.target()可以得到我们点击的对象
            e.target.style.color = 'aqua';
 
        })
    </script>
</body>

5、Event对象常用应用

5.1、event.preventDefault()

阻止默认行为

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

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

①、href="javascript:;"

<a href="javascript:;"></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>

5.2、event.stopPropagation()

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

<div id="outer">
    <div id="inner">inner</div>
</div>
<script>
    const outer = document.getElementById('outer')
    const inner = document.getElementById('inner')

    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')
    }
    outer.onclick=function(){
        console.log('out')
    }
    inner.onclick=function(e){
        console.log('inner')
        e.stopPropagation()
    }
</script>
仅输出inner

5.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>

5.4、event.currentTarget & event.target

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

<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>
控制台依次输出:
e.target: id4, e.currentTarget:id4
e.target: id4, e.currentTarget:id3
e.target: id4, e.currentTarget:id2
e.target: id4, e.currentTarget:id1

6、面试题-DOM事件有哪些级别

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

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

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

DOM0级事件

el.onclick = 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>

DOM2级事件

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

  • event-name:事件名称,可以是标准DOM事件

  • callback:回调函数,当事件触发时,函数会被注入一个参数(event)

  • useCapture:默认是false,代表事件处于冒泡阶段, true则为捕获阶段

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

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

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

DOM3级事件

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

  • UI事件:当用户与页面上的元素交互时触发,如load,scroll

  • 焦点事件:当元素获得或失去焦点时触发,如focus,blur

  • 鼠标事件:当用户通过鼠标在页面执行操作时触发,如:click,mousedown/mouseup等

  • 滚轮事件:当使用鼠标滚轮时触发,如mousewheel

  • 键盘事件:当用户通过键盘触发页面操作时,如keyup,keydown

  • 合成事件:当为输入法编辑器输入字符时触发,compositionstart

  • 变动事件:当底层DOM结构发生变化时触发,如DOMsubtreeModified

  • DOM3级事件允许使用者自定义一些事件

猜你喜欢

转载自blog.csdn.net/m0_56698268/article/details/129794711