• 深入了解javascript事件流


    摘要:事件流这个东西是比较重要的,为了让自己更加理解js中的事件流,必须整理整理,梳理一下事件流的各种东西啊。本文大部分内容参考《javascript高级程序设计第三版》

     先来一段书里的原文:

      当浏览器发展到第四代时(IE4和Netscape Communicator 4),浏览器团队遇到一个很有意思的问题:页面的哪一部分会拥有特定的事件?想象下在一张纸上有一组同心圆,如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是一组圆。两家公司的开发团队在看待浏览器事件方面还是一致的。如果你单击了某个按钮,那么同时你也单击了按钮的容器元素,甚至整个页面。

        事件流描述的是从页面中接受事件的顺序。但有意思的是,IE和Netscape开发团队居然提出了两个截然相反的事件流概念。

      1、IE的事件流是 事件冒泡流,

       2、标准的浏览器事件流是 事件捕获流。

         不过addEventLister给出了第三个参数同时支持冒泡与捕获,下文将介绍

    事件冒泡

    ie 的事件流叫事件冒泡,也就是说事件的传播为:从事件开始的具体元素,一级级往上传播到较为不具体的节点。案例如下:

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>事件冒泡</title>
    </head>
    <body>
        <div>
            <p>点我</p>
        </div>
    </body>
    </html>
    复制代码

    当我们点击P元素时,事件是这样传播的:

    (1) p

    (2) div

    (3) body

    (4) html

    (5) document

    现代浏览器都支持事件冒泡,IE9、Firefox、Chrome和Safari则将事件一直冒泡到window对象。

    事件捕获

       Netscape团队提出的另一种事件流叫做事件捕获。它的原理刚好和事件冒泡相反,它的用意在于在事件到达预定目标之前捕获它,而最具体的节点应该是最后才接收到事件的。

    比如还是上面的案例,当点击P元素时,事件的传播方向就变成了这样:

    (1) document

    (2) html

    (3) body

    (4) div

    (5) p

    IE9、Firefox、Chrome和Safari目前也支持这种事件流模型,但是有些老版本的浏览器不支持,所以很少人使用事件捕获,而是用事件冒泡的多一点。

    DOM事件流

       "DOM2级事件"规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。

      首先发生的事件捕获,为截获事件提供机会。然后是实际的目标接受事件。最后一个阶段是时间冒泡阶段,可以在这个阶段对事件做出响应。以前面的例子,则会按下图顺序触发事件。

      图1-1(图片来源于课本,所以没有上面案例的p标签)

      在DOM事件流中,事件的目标在捕获阶段不会接受到事件。这意味着在捕获阶段,事件从document到p后就定停止了。

      下一个阶段是处于目标阶段,于是事件在p上发生,并在事件处理中被看成冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回document。  

         多数支持DOM事件流的浏览器都实现了一种特定的行为;即使“DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上操作事件

    事件处理程序

    事件就是用户或者浏览器自身执行某种动作,比如click、load、mouseover。
    而响应某个事件的函数就叫做事件处理程序(事件监听器),事件处理程序的名字以on开头,click=>onclick、load=>onload

    DOM2提供了两个方法来让我们处理和删除事件处理程序的操作:addEventListener()和removeEventListener
    1
    2
    3
    4
    5
    6
    7
    btn.addEventListener(eventType, function () {
    }, false);
     
    该方法应用至dom节点
    第一个参数为事件名
    第二个为事件处理程序
    第三个为布尔值,true为事件捕获阶段调用事件处理程序,false为事件冒泡阶段调用事件处理程序

    使用例子如下:

    复制代码
     var btn = document.getElementById('btn');
     btn.addEventListener('click', function () {
                 alert('事件捕获');
            }, true);
     btn.addEventListener('click', function () {
                alert('事件冒泡');
            }, false);
    复制代码

    依次弹出“事件捕获”和“事件冒泡”,在这里的第一反应就是,他们有先后顺序吗?

    于是我反过来写:

    复制代码
      var btn = document.getElementById('btn');
      btn.addEventListener('click', function () {
                alert('事件冒泡');
            }, false);
      btn.addEventListener('click', function () {
                 alert('事件捕获');
            }, true);
    复制代码

    依次弹出的是:“事件冒泡”和“事件捕获”;到底有没有先后顺序呢?继续往下走

    这次是这样的写法:

    复制代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>事件冒泡</title>
    </head>
    <body>
        <div>
            <p id="parEle">我是父元素    <span id="sonEle">我是子元素</span></p>
        </div>
    </body>
    </html>
    <script type="text/javascript">
    var sonEle = document.getElementById('sonEle');
    var parEle = document.getElementById('parEle');
    
    parEle.addEventListener('click', function () {
        alert('父级 冒泡');
    }, false);
    parEle.addEventListener('click', function () {
        alert('父级 捕获');
    }, true);
    
    sonEle.addEventListener('click', function () {
        alert('子级冒泡');
    }, false);
    sonEle.addEventListener('click', function () {
        alert('子级捕获');
    }, true);
    
    </script>
    复制代码

    当点击“我是子元素” 时,弹出的顺序是:“父级捕获”--》“子级冒泡”--》“子集捕获”--》“父集冒泡”;

    这里可以说明,当点击子元素时,父级的执行顺序是先捕获,后冒泡的。

      综合前面的代码,我们可以得出一个小小的结论:

      当容器元素及嵌套元素,即在捕获阶段又在冒泡阶段调用事件处理程序时:事件按DOM事件流的顺序执行事件处理程序,也就是如图1-1所示

      且当事件处于目标阶段时,事件调用顺序决定于绑定事件的书写顺序,按上面的例子为,先调用冒泡阶段的事件处理程序,再调用捕获阶段的事件处理程序。依次alert出“子集冒泡”,“子集捕获”。

    删除一个事件处理程序,用removeEventListener。例:

    var btn=document.getElementById('myBtn');
    var myFn=function(){
        alert(this.id);
    }
    btn.addEventListener('click',myFn,false);
    btn.removeEventListener('click',myFn,false);

    注意点:为了最大限度的兼容,大多是情况下都是将事件处理程序添加到事件冒泡阶段。不是特别需要,不建议在事件捕获阶段注册事件处理程序

    兼容ie浏览器写法:

    ie添加和删除事件处理程序的写法有点小区别,所以用以下代码可以做个兼容性处理:

    复制代码
    var EventUtil = {
        addHandler: function (el, type, handler) {
            if (el.addEventListener) {
                el.addEventListener(type, handler, false);
            } else {
                el.attachEvent('on' + type, handler);
            }
        },
        removeHandler: function (el, type, handler) {
            if (el.removeEventListener) {
                el.removeEventListerner(type, handler, false);
            } else {
                el.detachEvent('on' + type, handler);
            }
        }
    };
    复制代码

    用法和前面的类似:

    EventUtil.addHandler('btn','click',handler);

    事件对象

    触发dom上的某个事件时,会产生一个事件对象,里面包含着所有和事件有关的信息。

    比较常用的有以下几个:

    currentTarget   事件处理程序当前正在处理事件的那个元素(始终等于this)
     
    preventDefault  取消事件默认行为,比如链接的跳转
     
    stopPropagation 取消事件冒泡
     
    target  事件的目标

      

    兼容ie的事件对象:

    复制代码
    var EventUtil = {
        addHandler: function (el, type, handler) {
            if (el.addEventListener) {
                el.addEventListener(type, handler, false);
            } else if (el.attachEvent) {
                el.attachEvent('on' + type, handler);
            } else {
                el['on' + type] = handler;
            }
        },
        removeHandler: function (el, type, handler) {
            if (el.removeEventListener) {
                el.removeEventListerner(type, handler, false);
            } else if (el.detachEvent) {
                el.detachEvent('on' + type, handler);
            } else {
                el['on' + type] = null;
            }
        },
        getEvent: function (e) {
            return e ? e : window.event;
        },
        getTarget: function (e) {
            return e.target ? e.target : e.srcElement;
        },
        preventDefault: function (e) {
            if (e.preventDefault) {
                e.preventDefault();
            } else {
                e.returnValue = false;
            }
        },
        stopPropagation: function (e) {
            if (e.stopPropagation) {
                e.stopPropagation();
            } else {
                e.cancelBubble = true;
            }
        }
    };
    复制代码

    其他

    html5事件之 beforeunload

    在页面卸载前触发,就像编辑博客园文章未保存是弹出的提示框一样。

    html5事件之 DOMContentLoaded事件

    支持页面下载前添加事件,而不需要等待图片,css文件,或者其他文件加载完毕才执行。可以让用户能够尽早的与用户进行交互。

    EventUtil.addHandler(document,'DOMContentLoaded',function(){
        alert('我可以先执行,哈哈')
    })

    事件委托:

       每个函数都是对象,都会占用内存,内存中的对象越多,性能就越差。对事件处理程序过多问题的解决方案就是事件委托。

       事件委托利用事件冒泡,只指定一个事件处理程序即可,就可以管理某一个类型的所有事件。例如:

    有三个li,都需要一个click事件,此时不需要给每个li都绑定click事件,主要给他的父级 ul增加一个绑定事件即可。这样点击li,利用冒泡,直接触发ul的click,只要判断是哪个li的id

    点击即可。而不需要三个li都绑定click事件。

    <ul id="myLinks">
        <li id="myLi1">text1</li>
        <li id="myLi2">text2</li>
        <li id="myLi3">text3</li>
    </ul>

    小小总结,有误之处,欢迎指出

  • 相关阅读:
    lintcode197- Permutation Index- easy
    lintcode10- String Permutation II- medium
    lintcode211- String Permutation- easy
    lintcode51- Previous Permutation- medium
    lintcode52- Next Permutation- medium
    lintcode108- Palindrome Partitioning II- medium
    lintcode136- Palindrome Partitioning- medium
    lintcode153- Combination Sum II- medium
    lintcode521- Remove Duplicate Numbers in Array- easy
    lintcode135- Combination Sum- medium
  • 原文地址:https://www.cnblogs.com/libin-1/p/5772542.html
Copyright © 2020-2023  润新知