快速了解DOM事件模型

264 阅读3分钟

事件是用户或浏览器自己执行的某种动作,是文档或者浏览器发生的一些交互瞬间,比如点击(click)按钮等。JS与html之间的交互式通过事件实现的 DOM 支持大量事件

<html>
    <body>
        <div id="div1">
            <div id="div2">
                <button></button>
            </div>
        </div>
    </body>
</html>

当点击按钮button,页面上哪些元素会出发这个事件,上面的div2,div1等是否也会出发这个点击事件。

这就牵扯到事件流,从上面的思考,我们知道它描述的是事件触发顺序,那上文中是按钮和其容器元素都触发吗,它们谁先触发呢?这可不是确定的,得看是哪种类型的事件流了。

事件捕获

看上面的代码

首先按照 Window-->Document-->html-->body-->div1--->div2-->button顺序看有没有函数监听。即从外向内找监听函数,叫事件捕获

事件冒泡

然后按照 botton-->div2-->div1-->body-->html-->Document-->Window顺序看有没有函数监听。即从内向外找监听函数,叫事件冒泡

如图所示

DOM事件流

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

事件绑定API

W3C: xxx.baba.addEventListener('click', fn, bool), 如果bool不传或为falsy, 则fn使用事件冒泡, 反之则fn使用事件捕获。

如果bool不传或为falsy就让fn走冒泡,即当浏览器在冒泡阶段发现xxx有监听函数 就会调用fn并提供事件信息

如果bool为true就让fn走捕获 即当浏览器在捕获阶段发现xxx有监听函数 调用fn并提供事件信息

特例

只有一个div被监听(不考虑父子同时被监听)

fn分别在捕获阶段和冒泡阶段监听click事件

用户点击的元素就是开发者监听

 div.addEventLisenter('click', f1)
 div.addEventLisenter('click',f2,true)
 请问,f1先执行还是f2先执行?
 调换位置
 div.addEventLisenter('click',f2,true)
 div.addEventLisenter('click', f1) 
 谁先执行
 错误答案:f2先执行
 正确答案:谁先监听谁先执行

取消冒泡

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

e.stopPropagation() 可以中断冒泡,浏览器不在往上走

不可阻止默认动作

有些事件不能阻止默认动作

MDN搜索scroll event

Bubbles-->该事件是否冒泡

Cancelable --> 开发者是否可以阻止默认事件

自定义事件

    <div id="div1">
        <button id="button1">点击出发frank事件</button>
    </div>
    <script>
        button1.addEventListener('click', () => {
            const event = new CustomEvent('frank', {
                detail: {
                    name: 'frank',
                    age: 10
                },
                bubbles: true,
                cancelable: false
            })
            button1.dispatchEvent(event) //触发事件
        })
        button1.addEventListener('frank', (e) => {
            console.log(e.detail)
        })
    </script>

事件委托(事件代理)

把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务。事件委托的原理是DOM元素的事件冒泡

优点

  • 省监听数(内存)
<div id='div1'>
    <button>button1</button>
    <button>button2</button>
    <button>button3</button>
    ... 
</div>
//有n多个button

如上面代码所示,如果给每个button都绑定一个函数,那对内存的消耗是非常大的,因此较好的解决办法是将button的点击事件绑定到它的父元素div1上,执行事件的时候再去匹配判断的目标元素

  • 可以监听动态元素

例子

<div id="div1"></div>

//过一会添加button
setTimeout(()=>{
    const button = document.createElement('button')
    button.textContent = 'click1'
    div1.appendChild(button)
},1000)

//事件委托
div1.addEventListenter('click',(e)=>{
    const t = e.target
    if(t.tagName.toLowerCase() === 'button'){
        console.log('button 被 click')
    }
})


//封装一下

on('click','#div1','button',()=>{
    console.log('button 被点击了')
})

function on(eventType, element, selector, fn){
    if(!(element instanceof Element)){
        element = document.querySelector(element)
    }
    element.addEventListener(eventType,(e)=>{
        cons t = e.target
        if(t.matches(selector)){
            fn(e)
        }
    })
}

//Element.matches() 一个元素是不是button,li,div