浏览器事件机制和事件代理

247 阅读4分钟

事件机制

定义:

浏览器的事件机制指的是事件在DOM里的传递规则。

浏览器的事件传递规则大致分为3个阶段

  • 捕获阶段
  • 目标阶段
  • 冒泡阶段

除了目标阶段,事件传播的顺序大致是这样的:

捕获阶段 => 目标阶段(先捕获还是先冒泡看对应注册事件的先后) => 冒泡阶段

在之前我们先说下注册事件的方法addEventListener,这个方法包括2个必选参数和2个可选参数,这里只说第三个参数是useCapture的情况,也就是true代表捕获阶段触发,false代表冒泡阶段触发。 先看个例子:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body,
        html {
            height: 100%;
            width: 100%;
        }
        
        #div1 {
            width: 600px;
            height: 400px;
            background-color: grey;
        }
        
        #div2 {
            width: 400px;
            height: 300px;
            background-color: lightblue;
        }
        
        #div3 {
            width: 300px;
            height: 200px;
            background-color: rgb(21, 84, 105);
        }
    </style>
    <script src="https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js"></script>
</head>

<body>
    <div id="div1">
        <div id="div2">
            <div id="div3"></div>
        </div>
    </div>
    <script>
        window.addEventListener('click', function(e) {
            console.log('window 捕获')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, true)
        document.addEventListener('click', function(e) {
            console.log('document 捕获')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, true)
        document.documentElement.addEventListener('click', function(e) {
            console.log('documentElement 捕获')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, true)
        document.body.addEventListener('click', function(e) {
            console.log('body 捕获')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, true)
        document.getElementById('div1').addEventListener('click', function(e) {
            console.log('div1 冒泡')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, false)
        document.getElementById('div1').addEventListener('click', function(e) {
            console.log('div1 捕获')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            
            console.log('----------------------')
        }, true)
        document.getElementById('div2').addEventListener('click', function(e) {
            console.log('div2 捕获')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, true)
        document.getElementById('div3').addEventListener('click', function(e) {
            console.log('div2 捕获')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)

            console.log('----------------------')
        }, true)



        window.addEventListener('click', function(e) {
            console.log('window 冒泡')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, false)
        document.addEventListener('click', function(e) {
            console.log('document 冒泡')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, false)
        document.documentElement.addEventListener('click', function(e) {
            console.log('documentElement 冒泡')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, false)
        document.body.addEventListener('click', function(e) {
                console.log('body 冒泡')
                console.log('e.target', e.target)
                console.log('e.currentTarget', e.currentTarget)
                console.log('e.eventPhase', e.eventPhase)
                console.log('----------------------')
            }, false)
            // document.getElementById('div1').addEventListener('click', function(e) {
            //     console.log('div1 冒泡')
            //     console.log('e.target', e.target)
            //     console.log('e.currentTarget', e.currentTarget)
            //     console.log('e.eventPhase', e.eventPhase)
            //     console.log('----------------------')
            // }, false)
        document.getElementById('div2').addEventListener('click', function(e) {
            console.log('div2 冒泡')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, false)
        document.getElementById('div3').addEventListener('click', function(e) {
            console.log('div2 冒泡')
            console.log('e.target', e.target)
            console.log('e.currentTarget', e.currentTarget)
            console.log('e.eventPhase', e.eventPhase)
            console.log('----------------------')
        }, false)
    </script>
</body>

</html>

别看那么一大段,其实就是从最外层的元素一直到最内层分别注册click事件。 用鼠标点击div1看下打印结果:

test.html:44 window 捕获
test.html:45 e.target <div id=​"div1">​…​</div>​
test.html:46 e.currentTarget Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
test.html:47 e.eventPhase 1
test.html:48 ----------------------
test.html:51 document 捕获
test.html:52 e.target <div id=​"div1">​…​</div>​
test.html:53 e.currentTarget #document
test.html:54 e.eventPhase 1
test.html:55 ----------------------
test.html:58 documentElement 捕获
test.html:59 e.target <div id=​"div1">​…​</div>​
test.html:60 e.currentTarget <html lang=​"en">​<head>​…​</head>​<body>​…​</body>​</html>​
test.html:61 e.eventPhase 1
test.html:62 ----------------------
test.html:65 body 捕获
test.html:66 e.target <div id=​"div1">​…​</div>​
test.html:67 e.currentTarget <body>​…​</body>​
test.html:68 e.eventPhase 1
test.html:69 ----------------------
test.html:72 div1 冒泡
test.html:73 e.target <div id=​"div1">​…​</div>​
test.html:74 e.currentTarget <div id=​"div1">​…​</div>​
test.html:75 e.eventPhase 2
test.html:76 ----------------------
test.html:79 div1 捕获
test.html:80 e.target <div id=​"div1">​…​</div>​
test.html:81 e.currentTarget <div id=​"div1">​…​</div>​
test.html:82 e.eventPhase 2
test.html:84 ----------------------
test.html:126 body 冒泡
test.html:127 e.target <div id=​"div1">​…​</div>​
test.html:128 e.currentTarget <body>​…​</body>​
test.html:129 e.eventPhase 3
test.html:130 ----------------------
test.html:119 documentElement 冒泡
test.html:120 e.target <div id=​"div1">​…​</div>​
test.html:121 e.currentTarget <html lang=​"en">​<head>​…​</head>​<body>​…​</body>​</html>​
test.html:122 e.eventPhase 3
test.html:123 ----------------------
test.html:112 document 冒泡
test.html:113 e.target <div id=​"div1">​…​</div>​
test.html:114 e.currentTarget #document
test.html:115 e.eventPhase 3
test.html:116 ----------------------
test.html:105 window 冒泡
test.html:106 e.target <div id=​"div1">​…​</div>​
test.html:107 e.currentTarget Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
test.html:108 e.eventPhase 3
test.html:109 ----------------------

这里注意的是可通过stopPropagation阻止事件传播,preventDefault阻止默认事件。e.target代表触发事件的对象,e.currentTarget代表注册事件的对象。

事件代理

定义:

事件代理就是指某个DOM的事件通过冒泡转交给其他DOM上注册的相同事件的回调去处理。 例子:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body,
        html {
            height: 100%;
            width: 100%;
        }
        
        #div1 {
            width: 600px;
            height: 400px;
            background-color: grey;
        }
        
        #div2 {
            width: 400px;
            height: 300px;
            background-color: lightblue;
        }
        
        #div3 {
            width: 300px;
            height: 200px;
            background-color: rgb(21, 84, 105);
        }
    </style>
    <script src="https://cdn.bootcss.com/jquery/3.5.0/jquery.min.js"></script>
</head>

<body>
    <div id="div1">
        <div id="div2">
            <div id="div3"></div>
        </div>
    </div>
    <script>
        document.getElementById("div1").addEventListener('click', function(e) {
            console.log(`hi i am ${e.target.id}`)
        }, false)
    </script>
</body>

</html>

分别点击div1, div2, div3,查看打印结果:

test.html:44 hi i am div1
test.html:44 hi i am div2
test.html:44 hi i am div3

事件代理的优点:

  1. 减少内存占用
  2. 不需要给子节点注销事件

参考文献

segmentfault.com/a/119000000…

juejin.cn/post/684490…