1-JavaScript-ECMAScript核心-事件event

事件对象

https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent

当事件的响应函数出发时,浏览器都会传递一个对象作为回调函数的实参,这个实参就是事件对象,事件对象中存储了所有当前事件相关的信息,如:事件的触发者、触发时哪个按键被按下、触发时的鼠标坐标.......

来个示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .d1 {
            width: 50%;
            height: 100px;
            border: 1px solid red;
        }
        .d2 {
            margin-top: 20px;
            width: 50%;
            height: 50px;
            border: 1px solid green;
            line-height: 50px;
            text-align: center;
        }
    </style>
</head>
<body>
<h1>在红框内移动鼠标,绿框内会实时显示当前鼠标的坐标</h1>
<div class="d1"></div>
<div class="d2"></div>
<script>
    let d1 = document.getElementsByClassName('d1')[0];
    let d2 = document.getElementsByClassName('d2')[0];
    d1.onmousemove = function (event) {
        d2.innerHTML = `x = ${event.clientX}; y = ${event.clientY}`;
    }
</script>
</body>
</html>

事件冒泡

冒泡(bubble),指的是事件的向上传导,当元素上的某个事件被触发时,其祖先元素上的相同事件也会同时被触发。

冒泡的存在大部分情况下都是有利的,简化了我们的开发。

冒泡的发生只和元素的结构有关,和元素的位置无关。

要取消事件的冒泡要用到事件对象,取消冒泡有两种方式,这两种方式本质上是一样的:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #d1 {
            width: 100px;
            height: 100px;
            background-color: tomato;
        }

        #d2 {
            background-color: yellow;
            /* 冒泡的发生只和元素的结构有关,和元素的位置无关 */
            /*left: 200px;*/
            /*top: 200px;*/
            /*position: absolute;*/
        }
    </style>
</head>
<body>
<div id="d1">
    我是div标签
    <div id="d2">我是子标签</div>
</div>
<script>
    let body = document.body;
    let d1 = document.getElementById('d1');
    let d2 = document.getElementById('d2');

    body.onclick = function (event) {
        console.log('我是body标签的onclick事件');
    };
    d1.onclick = function (event) {
        console.log('我是d1 div标签的onclick事件');
    };
    d2.onclick = function (event) {
        // 方式一
        // event.stopPropagation();
        // 方式二
        event.cancelBubble = true;
        console.log('我是d2 div标签的onclick事件');
    };
</script>
</body>
</html>

事件绑定

使用属性来绑定事件时,一个元素上同时只能为一个事件绑定一个响应函数,如果同时为一个事件设置了多个响应函数,则后面会覆盖掉前面的,如下示例所以:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button id="d1">点我</button>
<script>

    let d1 = document.getElementById('d1');

    d1.onclick = function (event) {
        alert(1);
    };
    // 下面的事件会覆盖掉上面的事件
    d1.onclick = function (event) {
        alert(2);
    };
</script>
</body>
</html>

怎么解决上面事件被覆盖的问题呢?

可以通过addEventListener来处理,它有三个参数:

  1. 想要绑定的事件,注意,不要前缀on,即如,原来用onclick绑定一个点击事件,在addEventListener中,直接填写click就行了。
  2. 回调函数。
  3. 是否在捕获阶段触发事件,默认为false,这个参数下个小节再说!
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button id="d1">点我</button>
<script>

    let d1 = document.getElementById('d1');

    // 原来这么做
    // d1.onclick = function (event) {
    //     alert(1);
    // };
    // // 移除绑定事件
    // d1.onclick = null;

    // 现在这么做
    d1.addEventListener('click', function (event) {
        alert(1);
    });
    d1.addEventListener('click', function (event) {
        alert(2);
    });
    d1.addEventListener('click', function (event) {
        alert(3);
    });

    // removeEventListener专门用于移除addEventListener添加的事件,对别的绑定方式的事件无效
    // 但是移除时,需要指定事件本身和回调函数,也因此,如果需要移除事件,那么在添加事件时,就要把
    // 回调函数单独写出来,就像下面的示例一样
    function clickHandler(event) {
        alert(4);
    }

    d1.addEventListener('click', clickHandler);
    d1.removeEventListener('click', clickHandler);


</script>
</body>
</html>

事件传播

这个故事要从一个现象说起,你先运行下下面的示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #d1 {
            width: 400px;
            height: 400px;
            background-color: tomato;
            text-align: right;
        }

        #d2 {
            width: 300px;
            height: 300px;
            background-color: palegoldenrod;
            text-align: right;
        }

        #d3 {
            width: 200px;
            height: 200px;
            background: pink;
            text-align: right;
        }
    </style>
</head>
<body>
<div id="d1">
    1
    <div id="d2">
        2
        <div id="d3">3</div>
    </div>
</div>
<script>

    let d1 = document.getElementById('d1');
    let d2 = document.getElementById('d2');
    let d3 = document.getElementById('d3');
    d1.addEventListener('click', function (event) {
        alert(1);
    });

    d2.addEventListener('click', function (event) {
        alert(2);
    });
    d3.addEventListener('click', function (event) {
        alert(3);
    });


</script>
</body>
</html>

你会发现,事件的传播就是冒泡。

那么关于事件的传播,微软和网景有着不同的理解:

  • 微软认为,事件应该是由内向外传播,也就是先触发后代元素上的事件,再触发祖先元素上的事件,这就是事件冒泡。
  • 网景认为,事件应该由外向内传播,也就是先触发祖先元素上的事件,再触发后代元素上的事件,这就是事件捕获。

这俩大佬出现了理念上的分歧,那个时候存在感不强的和事佬w3c就要劝架,经过一番如此这般之后,搞了个折中方案,将事件分为三个阶段:

  1. 事件捕获。

    • 当你点击d3(上例的id为d3的div)时,按照w3c的规定,事件由外往内开始捕获,即老祖宗window-->document-->....>body-->d1-->d2-->d3,直至找到目标元素,谁是目标元素啊?谁触发的事件谁就是目标元素。

    • 当然,不同的厂商对于老祖宗的定义不太一样,有的以window为准,有的以document......当然,主流还是window对象。

  2. 目标元素。事件捕获到目标元素,捕获停止。

  3. 事件冒泡:

    • 当找到目标元素后,开始触发由内向外进行事件冒泡。
    • 顺序:d3-->d2-->d1-->body--....window

但从上例的现象来看,只有事件冒泡现象,而没有事件捕获现象。

注意,无论怎么着,事件只能触发一次!你总不能事件捕获触发一次,事件冒泡再触发一次吧!所以,捕获和冒泡只能二选一,默认情况下是事件冒泡,即由内而外触发事件。

那么你就想看事件捕获的现象怎么办?好办!还记得addEventListener的第三个参数么?默认为false就表示事件触发以冒泡为准。所以,我们给第三个参数传递个true就是事件捕获了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #d1 {
            width: 400px;
            height: 400px;
            background-color: tomato;
            text-align: right;
        }

        #d2 {
            width: 300px;
            height: 300px;
            background-color: palegoldenrod;
            text-align: right;
        }

        #d3 {
            width: 200px;
            height: 200px;
            background: pink;
            text-align: right;
        }
    </style>
</head>
<body>
<div id="d1">
    1
    <div id="d2">
        2
        <div id="d3">3</div>
    </div>
</div>
<script>

    let d1 = document.getElementById('d1');
    let d2 = document.getElementById('d2');
    let d3 = document.getElementById('d3');
    d1.addEventListener('click', function (event) {
        alert(1);
    }, true);

    d2.addEventListener('click', function (event) {
        alert(2);
    }, true);
    d3.addEventListener('click', function (event) {
        alert(3);
    }, true);

</script>
</body>
</html>

但事件捕获的现象怪怪的,所幸,用的不多,理解就好了。

事件委派

先来看一个现象:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #d1 {
            width: 400px;
            height: 400px;
            background-color: tomato;
            text-align: right;
        }

        #d2 {
            width: 300px;
            height: 300px;
            background-color: palegoldenrod;
            text-align: right;
        }

        #d3 {
            width: 200px;
            height: 200px;
            background: pink;
            text-align: right;
        }
    </style>
</head>
<body>
<input type="text" placeholder="输入网站名称" name="c" id="inp">
<button id="btn">添加网站到列表</button>
<ul id="ul">
    <li><a href="javascript:;">搜狗</a></li>
    <li><a href="javascript:;">网易</a></li>
    <li><a href="javascript:;">百度</a></li>
</ul>
<script>

    let btn = document.getElementById('btn');
    let inp = document.getElementById('inp');
    let ul = document.getElementById('ul');
    let links = document.getElementsByTagName('a');
    // 为每个a标签绑定一个事件,点击连接,打印其内的文本
    for (let i = 0; i < links.length; i++) {
        links[i].addEventListener('click', function (event) {
            console.log(this.innerHTML);
        })
    }

    btn.addEventListener('click', function (event) {
        ul.insertAdjacentHTML('beforeend', `<li><a href="javascript:;">${inp.value}</a></li>`);
    });
</script>
</body>
</html>

上例代码存在问题:

  • 目前的事件绑定的代码写在了for循环中,for循环执行了几次就绑定了几个事件,同时产生了几个回调函数,但是回调函数的功能是一样的,这样做比较浪费内存。
  • 当前的事件只会绑定给已有的元素,对于新增加的元素来说,想要触发同样的事件,需要手动绑定。

而我们希望绑定事件只发生一次,就可以应用到所有的元素上,包括新增的元素。

这就用到了事件委派,也叫事件委托:当多个元素绑定相同的响应函数时,可以统一将事件绑定给它们的共同的祖先节点。这样只需绑定一次即可让所有的元素都具有该事件,及时元素是新增的也会具有该事件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        #d1 {
            width: 400px;
            height: 400px;
            background-color: tomato;
            text-align: right;
        }

        #d2 {
            width: 300px;
            height: 300px;
            background-color: palegoldenrod;
            text-align: right;
        }

        #d3 {
            width: 200px;
            height: 200px;
            background: pink;
            text-align: right;
        }
    </style>
</head>
<body>
<input type="text" placeholder="输入网站名称" name="c" id="inp">
<button id="btn">添加网站到列表</button>
<ul id="ul">
    <li><a href="javascript:;">搜狗</a></li>
    <li><a href="javascript:;">网易</a></li>
    <li><a href="javascript:;">百度</a></li>
</ul>
<script>

    let btn = document.getElementById('btn');
    let inp = document.getElementById('inp');
    let ul = document.getElementById('ul');
    let links = document.getElementsByTagName('a');
    ul.addEventListener('click', function (event) {
        // 这里不能用this,因为这里的this代指的是ul标签
        // 在事件对象中的target属性表示触发事件的对象
        // 如果点击的是a标签
        if (event.target.tagName.toUpperCase() === 'A') {
            console.log(event.target.innerHTML);
        }
    });

    btn.addEventListener('click', function (event) {
        console.log(111, inp.value);
        if (inp.value === "") {
            alert("网站名称为空,请重新输入");
        } else {
            ul.insertAdjacentHTML('beforeend', `<li><a href="javascript:;">${inp.value}</a></li>`);
        }

    });
</script>
</body>
</html>

常见事件

不同的事件有很多,所以,这里参考:https://developer.mozilla.org/zh-CN/docs/Web/Events

下面举一个键盘事件的例子,按键盘的上下左右键,来移动div标签。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        body {
            margin: 0;
            padding: 0;
        }
        #d1 {
            width: 100px;
            height: 100px;
            background-color: tomato;
            text-align: center;
            line-height: 100px;
            position: absolute;
        }
    </style>
</head>
<body>
<div id="d1"></div>
<script>
    let div = document.getElementById('d1');
    // 绑定全局的键盘事件
    document.addEventListener('keydown',function (event) {
        div.innerText = event.key;
        switch (event.key) {
            case 'ArrowUp':
            case 'Up':
                div.style.top = div.offsetTop - 10 + 'px';
                break;
            case 'ArrowDown':
            case 'Down':
                div.style.top = div.offsetTop + 10 + 'px';
                break;
            case 'ArrowLeft':
            case 'Left':
                div.style.left = div.offsetLeft - 10 + 'px';
                break;
            case 'ArrowRight':
            case 'Right':
                div.style.left = div.offsetLeft + 10 + 'px';
                break;
        }
    })
</script>
</body>
</html>

移入移出

移入移出共有两组四个方法:

  • onmouseenter/onmouseleave,移入/移出,用的较多。

  • onmouseover/onmouseout,移入/移出,跟上面有区别的就是,这俩货在经过子元素时,也会触发事件,不懂的话,看示例就能明白了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .out {
            position: absolute;
            width: 200px;
            height: 200px;
            top: 20px;
            left: 10px;
            background: pink;
        }

        .inner {
            position: absolute;
            width: 100px;
            height: 100px;
            top: 50px;
            background: skyblue;
        }
    </style>
</head>
<body>
<div class="out">
    外部DIV
    <div class="inner">内部div</div>
</div>
<script>
    let out = document.getElementsByClassName('out')[0];
    // 第一组,onmouseenter/onmouseleave
    // out.onmouseenter = function () {
    //     console.log('onmouseenter移入');
    // };
    // out.onmouseleave = function () {
    //     console.log('onmouseleave移出');
    // }

    // 第二组,onmouseover/onmouseout
    out.onmouseover = function () {
        console.log('onmousemove移入');
    };
    out.onmouseout = function () {
        console.log('onmouseout移出');
    }
</script>
<br>
</body>
</html>
posted @ 2022-02-15 11:44  听雨危楼  阅读(70)  评论(0编辑  收藏  举报