JS-DOM事件流

1,327 阅读6分钟

写在前面的话

其实很久之前就接触过DOM事件流了,但是在很长时间没有用到之后,就把它忘记了,所以今天特意来写一下,顺便回顾一下DOM时间流的一些知识

DOM事件流的分类

DOM事件流是有两种的,一种是捕获型事件流,另外一种是冒泡型事件流,两者其实都很好理解,下面我们就来详细介绍一下,为了大家更好的理解,我们就先介绍下冒泡型事件流

  1. 冒泡型事件流 "冒泡":没错就是你心中想的,冒泡就是我们平时可以常见的,例如水中的气泡往上冒,这就是冒泡,所以冒泡型事件流,就是当你点击目标元素的时候,当前所触发的一些事件会向父元素中传递,这就是所谓的事件冒泡,如果大家还是有不理解的地方,可以直接看后面的代码
  2. 捕获型事件流 捕获型事件流正好是与冒泡型事件流相反的,当你点击目标元素的时候,在该目标元素上点击触发的事件,会从父元素向下传递
  3. 其实,DOM标准规定事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。 事件捕获阶段:实际目标在捕获阶段不会接收事件。也就是在捕获阶段,事件从document到再到就停止了。 处于目标阶段:事件在上发生并处理。但是事件处理会被看成是冒泡阶段的一部分。 冒泡阶段:事件又传播回文档。

要点

在我们讨论详细了解之前,我们首先要弄理清一些知识点

  1. 在事件流中我们必须要记住一个结论,先捕获后冒泡
  2. 冒泡是默认开启的,那么我们改如何开启捕获呢,这时我们可以去js圣经中查找一下,addEventListener是支持三个参数的
1. event: 当前事件是什么事件,click,onmouseover?
2. callback: 当前事件所执行的回调
3. option | useCapture: 第三个参数是比较复杂的,
有两种情况,当第三个参数只传一个Boolean值时,此时就可以打开捕获模式,
当然第三个参数还可以传入一个对象,这个大家可以去MDN上看一下,面试的时候还是会公司问的
    options: {
         capture
         once: 是否只执行一次当前回调
         passive:表示当前监听器永远不会调用preventDefault函数
         最后一个option的话,因为有一些兼容性,就不在这里介绍了
     } 

代码

  1. 冒泡
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
    <div>
        <p>
            <a href="void: javascript(0)">点我让你看事件捕获</a>
        </p>
    </div>
</body>
<script>
    $('div').click(function() {
        console.log(2)
    })
    $('p').click(function() {
        console.log(1)
    })
    $('a').click(function() {
        console.log(0)
    })
</script>
</html>

image
当点击a标签的时候,此时会按照0--1--2的顺序输出,这就是事件冒泡 2. 事件捕获

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
    <div>
        <p>
            <a href="void: javascript(0)">
                <span>123</span>
            </a>
        </p>
    </div>
</body>
<script>
    let div = document.getElementsByTagName('div')[0]
    let p = document.getElementsByTagName('p')[0]
    let a = document.getElementsByTagName('a')[0]
    let span = document.getElementsByTagName('span')[0]

    div.addEventListener("click", function() {
        console.log(2)
    })
    p.addEventListener("click", function() {
        console.log(1)
    })
    a.addEventListener("click", function() {
        console.log(0)
    }, true)
    span.addEventListener("click", function() {
        console.log(-1)
    })
</script>
</html>

image
这个地方大家要注意看,我们对a绑定click事件的时候,给第三个参数设置为true了,表示该事件在捕获阶段触发, 所以我们看到的输出结果是0 -- -1 -- 1 -- 2,这也是验证了我们之前的结论,事件流是先捕获后冒泡的

如何阻止?

阻止事件流

事件捕获我们可以通过addEventListener的第三个参数控制开关,但是事件冒泡我们该如何阻止呢,难道任由事件冒泡出现吗?幸运的是我们有stopPropagation这个函数,这个函数就是用来阻止事件冒泡的(其实也可以用来阻止事件捕获),先上代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
    <div>
        <p>
            <a href="void: javascript(0)">
                <span>123</span>
            </a>
        </p>
    </div>
</body>
<script>
    let div = document.getElementsByTagName('div')[0]
    let p = document.getElementsByTagName('p')[0]
    let a = document.getElementsByTagName('a')[0]
    let span = document.getElementsByTagName('span')[0]

    div.addEventListener("click", function() {
        console.log(2)
    })
    p.addEventListener("click", function(e) {
        e.stopPropagation()
        console.log(1)
    })
    a.addEventListener("click", function(e) {
        console.log(0)
        // e.stopPropagation()

    })
    span.addEventListener("click", function() {
        console.log(-1)
    })
</script>
</html>

image
我们可以看到,打印的是-1 0 1,到1的时候已经阻止事件向上传播了,也就是说已经成功的阻止事件冒泡了。 上面我们可以看到,这个函数可以阻止冒泡,那么它只有这么一个功能吗,答案是否定的,来我们看下面一段代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
    <div>
        <p>
            <a href="void: javascript(0)">
                <span>123</span>
            </a>
        </p>
    </div>
</body>
<script>
    let div = document.getElementsByTagName('div')[0]
    let p = document.getElementsByTagName('p')[0]
    let a = document.getElementsByTagName('a')[0]
    let span = document.getElementsByTagName('span')[0]

    div.addEventListener("click", function() {
        console.log(2)
    })
    p.addEventListener("click", function(e) {
        e.stopPropagation()
        console.log(1)
    }, true)
    a.addEventListener("click", function(e) {
        console.log(0)
    })
    span.addEventListener("click", function() {
        console.log(-1)
    })
</script>
</html>

我们可以看到的是,控制台的打印是只有1的,这也就是说,这个函数不仅仅是用于阻止事件冒泡,它还可以用于阻止事件捕获,更可以说,事件流的三个阶段它都能阻止,这就是他的作用

那么除了使用stopPropagation()之外,我们还有其他的方法阻止吗,大家猜的不错,我们确实还有一种方法,来阻止事件流,那就是通过return false,这个方法是有一些缺陷的,他是只有在jQuery中才能生效,在原生js中只能是**阻止默认行为(注意这个名词,后面我们会提到)**的,先上代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
    <div>
        <p>
            <a href="void: javascript(0)">点我让你看事件捕获</a>
        </p>
    </div>
</body>
<script>
    $('div').click(function() {
        console.log(2)
    })
    $('p').click(function() {
        console.log(1)
        return false
    })
    $('a').click(function() {
        console.log(0)
    })
</script>
</html>

image
我们可以看到控制台的打印结果只有0 -- 1所以我们认证了return false是可以阻止冒泡的

阻止默认行为

上面我们在讲return false的时候,我们提到了一个名词,阻止默认行为,那么什么是默认行为呢,我们举个栗子,当我们点击a标签的时候,它会自动跳转到href对应的地址,这个跳转就叫默认行为,我们可以通过return false来阻止一些默认行为 除此之外,还有另一种方法就是通过调用preventDefault方法来实现,这个函数也是用来阻止默认事件的

注意点

我们在使用preventDefault和stopPropagation的时候,是会有兼容性的,在IE中是不存在这两个函数的,那么在IE中我们是如何实现阻止冒泡和阻止默认事件的呢,看代码

//阻止冒泡事件
function stopBubble(e){
    if(e && e.stopPropagation){
        // 非IE浏览器
        e.stopPropagation();
    }else{ 
        //IE浏览器
        window.event.cancelBubble=true;
    }
}
// 阻止默认行为
//阻止浏览器默认行为
function stopDefault(e){ 
    //标准浏览器
    if(e && e.preventDefault){ 
        e.preventDefault(); 
    } 
    //个别IE
    else{ 
        window.event.returnValue=fale;
        return false;
    } 
}

通过以上代码,我们就可以完美的实现阻止冒泡和默认行为了

存在的意义

在我们讨论了这么长的事件捕获和事件冒泡之后,我们要想一下,为什么会有事件捕获和事件冒泡呢 这就是我们接下来要讨论的事件代理(事件委托),

那么什么是事件委托呢,事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件(--- 源自JavaScript高级程序设计),

那么为什么会出现事件委托呢,当我们有1个li的时候,我们给li加上click事件,这样是完全没有问题的,但是当我们有成百上千个li呢,此时我们会怎么处理,当然一种最简单的方法就是for循环遍历,然后给每个li都加上click事件,这样确实能实现,但是我们要想到的是,html页面的渲染速度是和dom的操作的多少挂钩的,而dom操作的多少会和绑定的事件的数量挂钩的,绑定的数量越多,渲染肯定是越慢的,那么此时你肯定会问了,有什么好的解决方法吗,此时我们就可以用到这个名词了,事件委托,有限事件委托的原理就是事件冒泡,当我们给li添加事件的时候,此时事件流会顺着li向外层的ul流去,那么此时我们便可以只在ul上添加和li上相同的事件,便可以实现和在li上添加的相同的效果,我们先上代码看一下

html 
<ul id="ul1">
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
</ul>
window.onload = function(){
    var oUl = document.getElementById("ul1");
    var aLi = oUl.getElementsByTagName('li');
    for(var i=0;i<aLi.length;i++){
        aLi[i].onclick = function(){
            alert(123);
        }
    }
}

上面这种方式是给每个li添加事件,这个毫无疑问会降低渲染速度,下面我们看一下事件代理之后的代码

window.onload = function(){
    var oUl = document.getElementById("ul1");
   oUl.onclick = function(){
        alert(123);
    }
}

我们仅仅需要绑定一个事件,变可以实现相同的效果,岂不是美哉?,所以这就是我们常说的事件代理

======================================================================= 分割线,DOM事件流已经分析完毕了, 如果有什么不正确的地方欢迎大家指出来,我是一只在前端路上前进的程序袁,而且我会一直前进下去的,希望能和大家共同进步 欢迎大家进我的github上去看一下,我的github