探秘JavaScript事件传播机制:冒泡、捕获与目标阶段解析

​🌈个人主页:前端青山
🔥系列专栏:JavaScript篇
🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来JavaScript篇专栏内容:JavaScript-事件传播

目录

事件 传播

阻止事件传播

默认行为

阻止浏览器默认行为

事件委托

target

封装事件库

事件 传播

  1. 浏览器内的事件流机制

    什么是事件的执行机制呢?

    • 思考一个问题?

    • 当一个大盒子嵌套一个小盒子的时候,并且两个盒子都有点击事件

    • 你点击里面的小盒子,外面的大盒子上的点击事件要不要执行

    • 当元素触发一个事件的时候,其父元素也会触发相同的事件,父元素的父元素也会触发相同的事件

  2. 事件捕获 / 目标 / 冒泡

    1. 事件捕获 : 从上到下、从祖先到子孙依次传递事件的过程

    2. 事件目标 : 触发事件的对象,你是点击在哪个元素身上了,那么这个事件的 目标 就是什么

    3. 事件冒泡 : 从下到上、从子孙到祖先依次传递事件的过程

    4. 浏览器默认启动了事件冒泡!!!

    5. IE和欧朋浏览器不支持事件捕获

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body{
            border: 1px solid black;
        }
        #box{
            width: 200px;
            height: 150px;
            background: pink;
        }
        .pox{
            width: 100px;
            height: 100px;
            background: yellow;
        }
    </style>
</head>
<body>
    <div id="box">
        <div class="pox"></div>
    </div>
    <script>
        //1. html
        var html = document.documentElement;
        //2. body
        var body = document.body;
        //3. box
        var box = document.querySelector('#box');
        //4. pox
        var pox = document.querySelector('.pox');
        
       
        document.onclick = function(){
            alert('document');
        }
        html.onclick = function(){
            alert('html');
        }
        body.onclick = function(){
            alert('body');
        }
        box.onclick = function(){
            alert('box');
        }
        pox.onclick = function(evt){
            var e = evt || window.event;
            //标准浏览器:阻止事件冒泡
            // e.stopPropagation();
            //IE浏览器:阻止事件冒泡
            // e.cancelBubble = true;
            //兼容
            e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
            alert('pox');
        }
    </script>
</body>
</html>

阻止事件传播

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body{
            border: 1px solid black;
        }
        #box{
            width: 200px;
            height: 150px;
            background: pink;
        }
        .pox{
            width: 100px;
            height: 100px;
            background: yellow;
        }
    </style>
</head>
<body>
    <div id="box">
        <div class="pox"></div>
    </div>
    <script>
        //1. html
        var html = document.documentElement;
        //2. body
        var body = document.body;
        //3. box
        var box = document.querySelector('#box');
        //4. pox
        var pox = document.querySelector('.pox');
        
       
        document.onclick = function(){
            alert('document');
        }
        html.onclick = function(){
            alert('html');
        }
        body.onclick = function(){
            alert('body');
        }
        box.onclick = function(){
            alert('box');
        }
        pox.onclick = function(evt){
            var e = evt || window.event;
            //标准浏览器:阻止事件冒泡
            // e.stopPropagation();
            //IE浏览器:阻止事件冒泡
            // e.cancelBubble = true;
            //兼容
            e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
            alert('pox');
        }
    </script>
</body>
</html>

标准浏览器: event.stopPropagation() IE浏览器: event.cancelBubble = true; 兼容:

//阻止事件冒泡的兼容
function stopPropagation(evt){
    var e = evt || window.event;
    e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;
}

默认行为

  • 默认行为,就是不用我们注册,它自己就存在的事情

    • 比如我们点击鼠标右键的时候,会自动弹出一个菜单

    • 比如我们点击 a 标签的时候,我们不需要注册点击事件,他自己就会跳转页面

  • 这些不需要我们注册就能实现的事情,我们叫做 默认事件

阻止浏览器默认行为

标准浏览器: event.preventDefault() IE浏览器: event.returnValue = false 兼容:

//阻止浏览器默认行为的兼容
function preventDefault(evt){
    var e = evt || window.event;
    e.preventDefault ? e.preventDefault() : e.returnValue = false;
}

return false : 既阻止默认行为,也阻止事件冒泡!

    <script>
        //获取ul
        var ul = document.querySelector('ul');
        //右键菜单事件 oncontextmenu
        document.oncontextmenu = function(evt){
            var e = evt || window.event;
            //标准浏览器:阻止默认行为
            // e.preventDefault();
            //IE浏览器:阻止默认行为
            // e.returnValue = false;
            //兼容
            e.preventDefault ? e.preventDefault() : e.returnValue = false;
            // alert('右键菜单已禁用!');
            ul.style.display = 'block';
            ul.style.left = e.pageX + 'px';
            ul.style.top = e.pageY + 'px';
        }
        document.onclick = function(){
            ul.style.display = 'none';
        }
    </script>
    <script>
        //获取a
        var a = document.querySelector('a');
        //点击事件
        a.onclick = function(event){
            // e = event || window.event;
            // e.preventDefault ? e.preventDefault() : e.returnValue = false;
            return false; // 既阻止默认行为,也阻止事件冒泡
        }
    </script>

事件委托

  • 就是把我要做的事情委托给别人来做

  • 因为我们的冒泡机制,点击子元素的时候,也会同步触发父元素的相同事件

  • 所以我们就可以把子元素的事件委托给父元素来做

将加到子元素上的事件,添加到父元素上,为了提高性能。原理是利用了事件冒泡。

  1. 实现事件委托

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <h4>hhhhhhhhhhhh</h4>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <p>pppppppppppp</p>
        <li>8</li>
        <li>9</li>
        <li>10</li>
    </ul>
    <script>
        // //获取所有的li
        // var li = document.querySelectorAll('ul>li');
        // //遍历,添加事件
        // for(var i = 0,len = li.length;i < len;i ++){
        //     li[i].onclick = function(){
        //         alert(this.innerText);
        //     }
        // }

        //获取ul
        var ul = document.querySelector('ul');
        //添加事件
        ul.onclick = function(evt){
            var e = evt || window.event;
            //获取事件源
            //标准浏览器
            // var target = e.target;
            //IE浏览器
            // var target = e.srcElement;
            var target = e.target || e.srcElement;
            //过滤
            if(target.nodeName === 'LI'){
                alert(target.innerText);
            }
            // alert(this.innerText);
        }
        var o_li = document.createElement('li');
        o_li.innerText = 11;
        ul.appendChild(o_li);
    </script>
</body>
</html>

事件添加到父元素 通过事件对象获取事件源 进行过滤

  1. 事件源

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <h4>hhhhhhhhhhhh</h4>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <p>pppppppppppp</p>
        <li>8</li>
        <li>9</li>
        <li>10</li>
    </ul>
    <script>
        
// 已知页面上有结构ul,内有10个li,每个li的内容不同,请使用事件委托的方式给每个li都绑定点击事件,点击的时候打印对应li的内容

        //获取ul
        var ul = document.querySelector('ul');
        //添加事件
        ul.onclick = function(evt){
            var e = evt || window.event;
            //获取事件源
            //标准浏览器
            // var target = e.target;
            //IE浏览器
            // var target = e.srcElement;
            var target = e.target || e.srcElement;
            //过滤
            if(target.nodeName === 'LI'){
                alert(target.innerText);
            }
            // alert(this.innerText);
        }
        var o_li = document.createElement('li');
        o_li.innerText = 11;
        ul.appendChild(o_li);
    </script>
</body>
</html>

target

  • target 这个属性是事件对象里面的属性,表示你点击的目标

  • 当你触发点击事件的时候,你点击在哪个元素上,target 就是哪个元素

  • 这个 target 也不兼容,在 IE 下要使用 srcElement

标准浏览器: event.target IE浏览器: event.srcElement

// 获取事件源的兼容
function getTarget(evt){
    var e = evt || window.event;
    return e.target || e.srcElement;
}

封装事件库

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box{
            width: 100px;
            height: 100px;
            background: red;
        }
    </style>
</head>
<body>
    <div id="box"></div>
    <script>
        //callBack : 回调
        function bindEvent(dom,tapCB,clickCB){
            //记录触摸开始的时间
            var startTime = 0;
            //是否移动了手指
            var isMove = false;
            //监听触摸开始事件
            dom.addEventListener('touchstart',function(){
                //当前时间
                startTime = Date.now();
            })
            dom.addEventListener('touchmove',function(){
                isMove = true;
            })
            dom.addEventListener('touchend',function(){
                //如果触摸的时间差 <= 150 && 移动了
                if(Date.now() - startTime <= 150 && isMove){
                    //轻触
                    if(tapCB instanceof Function){
                        tapCB();
                    }
                }else{
                    //点击
                    if(typeof clickCB === 'function'){
                        clickCB();
                    }
                }
            })
        }

        //获取div
        var div = document.querySelector('#box');
        bindEvent(div,function(){
            console.log('我摸了一下');
        },function(){
            console.log('我点击了一个div');
        })
    </script>
</body>
</html>
  1. 移动端tap事件

    // 封装事件
    // dom : 事件源,触发事件的对象
    // tapCallback : 轻击后的回调函数,轻击后想要执行什么?
    // clickCallback : 点击后的回调函数,点击后想要执行什么?
    var bindTapEvent = function(dom, tapCallback, clickCallback) {
        // 声明变量-开始时间
        var startTime = 0;
        // 声明变量-记录是否移动-默认为false,没有移动
        var isMove = false;
        //监听触摸开始事件
        dom.addEventListener('touchstart', function(e) {
            //记录触摸后的时间
            startTime = Date.now()
        });
        //监听触摸移动事件
        dom.addEventListener('touchmove', function(e) {
            //移动后,记录为true
            isMove = true
        });
        //监听触摸结束事件
        dom.addEventListener('touchend', function(e) {
            //检测触摸时间 与 是否移动
            if ((Date.now() - startTime) < 150 && isMove) {
                // 假设点击的时间间隔小于150ms为轻击事件
                tapCallback && tapCallback.call(this, e)
            } else {
                // 假设点击的时间间隔大于150ms为点击事件
                clickCallback && clickCallback.call(this, e)
            }
            //开始时间恢复为0
            startTime = 0;
            //记录移动为false
            isMove = false;
        });
    }
  2. 移动端左滑右滑事件

/*
Touch事件:
​
touches:当前位于屏幕上的所有手指的一个列表
targetTouches:位于当前DOM元素上的手指的一个列表;
changedTouches:涉及当前事件的手指的一个列表;
screenX,screenY:触摸点相对于屏幕上边缘的坐标;
clientX,clientY:触摸点相对于浏览器的viewport左边缘的坐标,不包括左边的滚动距离;
pageX,pageY:触摸点相对于document的左边缘的坐标,与clientX不同的是它包括左边滚动的距离,如果有的话;
target:总是表示手指最开始放在触摸设备上的触发点所在位置的element。
*/
​
/**
 * 用touch事件模拟点击、左滑、右滑、上拉、下拉等事件,
 * 是利用touchstart和touchend两个事件发生的位置来确定是什么操作。
 * 例如:
 * 1、touchstart和touchend两个事件的位置基本一致,也就是没发生位移,那么可以确定用户是想点击按钮等。
 * 2、touchend在touchstart正左侧,说明用户是向左滑动的。
 * 利用上面的原理,可以模拟移动端的各类事件。
 **/
var EventUtil = (function() {
​
    //支持事件列表(左滑、右滑)
    var eventArr = ['eventswipeleft', 'eventswiperight'
    ];
​
    //touchstart事件,delta记录开始触摸位置
    function touchStart(event) {
        //声明空对象,用来记录触摸开始时的位置和时间信息
        this.delta = {};
        //添加x坐标值
        this.delta.x = event.touches[0].pageX;
        //添加y坐标值
        this.delta.y = event.touches[0].pageY;
    }
​
    /**
     * touchend事件,计算两个事件之间的位移量
     * 1、如果位移量很小或没有位移,看做点击事件
     * 2、如果位移量较大,x大于y,可以看做平移,x>0,向右滑,反之向左滑。
     * 这样就模拟的移动端几个常见的时间。
     * */
    function touchEnd(event) {
        //记录开始时的位置时间信息
        var delta = this.delta;
        //删除开始时记录的信息
        delete this.delta;
        //计算坐标差值
        delta.x -= event.changedTouches[0].pageX;
        delta.y -= event.changedTouches[0].pageY;
        // 左右滑动
        if (Math.abs(delta.x) > Math.abs(delta.y)) {
            //左滑
            if (delta.x > 0) {
                this['eventswipeleft'].map(function(fn) {
                    fn(event);
                });
            } else { //右滑
                this['eventswiperight'].map(function(fn) {
                    fn(event);
                });
            }
        }
    }
    //绑定事件
    function bindEvent(dom, type, callback) {
        if (!dom) { //如果没有节点对象,则抛出错误
            console.error('dom is null or undefined');
        }
        //遍历数组,检测节点对象是否绑定过事件
        var flag = eventArr.some(function(key){
            return dom[key]
        });
        //未绑定过事件
        if (!flag) {
            //进行绑定事件
            dom.addEventListener('touchstart', touchStart);
            dom.addEventListener('touchend', touchEnd);
        }
        //如果节点事件为空
        if (!dom['event' + type]) {
            //添加空数组
            dom['event' + type] = [];
        }
        //将回调函数添加到节点事件的数组中
        dom['event' + type].push(callback);
    }
    
    return {
        bindEvent
    }
})();

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2023年12月21日
下一篇 2023年12月21日

相关推荐