• 11、JavaScript——事件 (转载)


    来源:JavaScript事件 详细讲解  ——独行冰海

            JavaScript 和事件——于江水

    需要了解事件的什么?

      对于事件来讲,首先,我们需要了解这样几个概念:事件 ;事件处理程序 ;事件类型 ;事件流 ;事件冒泡 ;事件捕获 ;事件对象 ;事件方面的性能优化(事件委托、移除事件处理程序);常见的浏览器兼容问题。

    1、事件的概念

      事件:指的是文档或者浏览器窗口中发生的一些特定交互瞬间。我们可以通过侦听器(或者处理程序)来预定事件,以便事件发生的时候执行相应的代码。

    2、事件处理程序

      事件处理程序:我们用户在页面中进行的点击这个动作,鼠标移动的动作,网页页面加载完成的动作等,都可以称之为事件名称,即:click、mousemove、load等都是事件的名称。响应某个事件的函数则称为事件处理程序,或者叫做事件侦听器。事件处理程序的名字是以“on”开头,因此click事件的处理程序就是onclick。

      接下来我们用一段代码来再说明一下上面这两个抽象的概念,具体解释见代码注释:

    <body>
        <div id="main">理解事件的基本概念</div>
    </body>
    <script>
        var main = document.getElementById('main');
        // 此处click,点击,是一种事件的名称,是浏览器窗口中发生点击的瞬间。on这个单词,其实就是让click这个事件得以相应,因此,onclick就叫做事件处理程序。下面这段代码,我们通
    过处理程序(onclick)为main这个元素预定了点击(click),这样在点击(click)发生的时候,执行函数中的代码(弹出一个对话框)。

    main.onclick
    = function(){ alert('点击了此处哦!'); } </script>

      onclick不是事件,click才是事件,或者称为是“click事件类型”。onclick是引用的是一个元素对象的属性,它指向“click事件类型”所绑定的事件处理函数。

      

      关于事件处理程序,从最初的发展到现在,经历了几次的变化。

    2.1 HTML事件处理程序--HTML内联属性

      在最初,使用的是HTML事件处理程序,也就是说,某个元素(如div),支持的每一种事件,都可以使用一个与相应事件处理程序同名的HTML特性来制定(也就是标签的一个属性),这个特性的值就是能够执行的JavaScript代码。例如:

    <body>
        <p onclick="alert('click')">HTML事件处理程序</p>
    </body>

      当然,我们也可以再onclick=” ”当中进行函数的调用。

      但是,无论哪种方法,都暴露出来HTML事件处理程序的很多问题:

    1. 首先,HTML代码域JavaScript代码紧密的耦合在一起,没有实现相互分离,在进行代码的更新与维护的时候就显得异常困难。
    2. 第二,扩展事件处理程序的作用域链在不同浏览器当中会导致不同的结果。
    3. 第三,如果不采用调用函数的方式,而是像例子中那样直接书写代码,那么代码的通用性很差,会使得整站的代码量很大,通用性差。如果提取出来,存放在函数当中,那么,会面临另一个问题——当函数还没有被定义,只是HTML、CSS代码加载完毕,用户进行点击,会完全没有反应。

     2.2 DOM0级事件处理程序--DOM属性绑定

      基于上面的这些问题,人们逐渐的去完善了事件处理程序,此时出现了DOM0级事件处理程序。(第四代WEB浏览器中出现)

      DOM0级事件处理程序是什么样子的呢?其实它就是我们平时最为常用的事件绑定,如下面这个例子:

    <body>
        <p id='btn'>DOM0级事件处理程序</p>
    </body>
    <script>
        var btn = document.getElementById('btn');
        btn.onclick = function(){
            alert(this.innerHTML);
        }
    </script>

      在DOM0级事件处理程序推出之后,广为各个用户的使用,但是,却出现了这样一个问题,当我希望为同一个元素/标签绑定多个同类型事件的时候(如,为上面的这个p标签绑定3个点击事件),是不被允许的,也就是如果你在后面代码中再次为 btn绑定一个回调函数,会覆盖掉之前回调函数的内容。那么,此时,出现了另一种事件处理程序,就是DOM2级的事件处理程序。

    2.3 DOM2级事件处理程序--使用事件监听函数

      在DOM2级当中,定义了两个基本方法,用于处理指定(即绑定)和删除事件处理程序的操作,分别是addEventListener()removeEventListener(),IE9+、FireFox、Safari、Chrome和Opera都是支持DOM2级事件处理程序的。对于IE8-,则使用的是IE专有的事件处理程序:两个类似的方法——attachEvent()与detachEvent()。

      具体实例代码如下:(以addEventListener为例,给出了两种书写方式)

    <body>
        <p id='btn'>DOM2事件处理程序</p>
    </body>
    <script>
        var btn = document.getElementById('btn');
        btn.addEventListener("click", test, false);   //当监听到click事件发生时,调用test函数
        function test(){
            alert(this.innerHTML);
        }
    
      //var btn = document.getElementById('btn');
        btn.addEventListener("click", function(){
            alert(this.innerHTML);        
        }, false);
    </script> 

      addEventListener()和removeEventListener()中的第三个参数,表示的是在哪个事件阶段进行事件处理,如果是false,则指的是冒泡阶段;如果是true,则指的是捕获阶段

      移除事件监听时,绑定事件的回调函数不能是匿名函数,必须是一个声明的函数,因为解除事件绑定时需要传递这个回调函数的引用,才可以断开绑定。例如:

    var fun = function() {
        // function logic
    };
    
    element.addEventListener('click', fun, false);
    element.removeEventListener('click', fun, false);

    3、事件类型

      之前在课程的讲解当中,我们把事件分为了三大类,分别是一般事件、表单事件和页面事件。当前我们可以再做细分:

    1. UI事件:如load、unload、error、resize、scroll、select、DOMActive,是用户与页面上的元素交互时触发的。
    2. 焦点事件:如blur、DOMFocusIn、DOMFocusOut、focus、focusin、focusout,在元素获得或失去焦点的时候触发,这些事件当中,最为重要的是blur和focus,有一点需要引起注意,这一类事件不会发生冒泡!
    3. 鼠标与滚轮事件:如click、dblclick、mousedown、mouseenter、mouseleave、mousemove、mouseout、mouseover、mouseup,是当用户通过鼠标在页面执行操作时所触发的。
    4. 滚轮事件:mousewheel(IE6+均支持)、DOMMouseScroll(FF支持的,与mousewheel效果一样)。是使用鼠标滚轮时触发的。
    5. 文本事件:textInput,在文档中输入文本触发。
    6. 键盘事件:keydown、keyup、keypress,当用户通过键盘在页面中执行操作时触发。
    7. 合成事件:DOM3级新增,用于处理IME的输入序列。所谓IME,指的是输入法编辑器,可以让用户输入在物理键盘上找不到的字符。compositionstart、compositionupdate、compositionend三种事件。
    8. 变动事件:DOMsubtreeModified、DOMNodeInserted、DOMNodeRemoved、DOMAttrModified、DOMCharacterDataModified等,当底层DOM结构发生变化时触发。IE8-不支持。
    9. 变动名称事件:指的是当元素或者属性名变动时触发,当前已经弃用!

      对于事件的基本类型,随着HTML5的出现和发展,又新增了HTML5事件、设备事件、触摸事件、手势事件等各种事件,在后面我们再详细介绍。

    4、事件流

      事件流:描述的是从页面中接收事件的顺序。

      IE与原来的NetScape(网景),对于事件流提出的是完全不同的顺序。IE团队提出的是事件冒泡流;NetScape的事件流是事件捕获流。

    4.1、事件冒泡

      事件冒泡:表示的是,事件开始的时候由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

    4.2、事件捕获

      事件捕获:表示的是,事件开始的时候由最不具体的节点接收,然后逐级向下传播到最具体的节点。

      来看一个实例:

    <body>
        <div id="main">
            <p>理解事件的基本概念</p>
        </div>
    </body>

      如果单击了p标签,那么,如果是事件冒泡流的事件流机制,则click事件将按照如下顺序进行执行:p —— div —— body —— html —— document。

      如果采用捕获流的事件流机制,则click事件的执行顺序为:document —— html —— body —— div —— p

      对于冒泡流的事件流机制,存在如下的兼容问题:

    1. <=IE5.5     p -> div -> body -> document
    2. >=IE6.0        p -> div -> body -> html -> document
    3. >=Mozilla 1.0    p -> div -> body -> html -> document -> window

    5、事件对象(Event object)

      事件对象:在触发DOM上的某个事件的时候,会产生一个事件对象event,而在这个对象当中会包含着所有与事件有关的信息。我们书写如下基本代码:

    <body>
        <div id="main">理解事件的基本概念</div>
    </body>
    <script>
        var main = document.getElementById('main');
        main.onclick = function(event){
            console.log(event);
        }
    </script>

      使用console.log打印出的结果如下图。

    JavaScript事件 详细讲解 - 独行冰海 - 独行冰海
     

      其中有两个信息,我们最为常用,分别是type和target。

      type表示的是被触发事件的类型;target表示的是事件的目标。其他信息,如:

    1. bubbles:表示事件是否冒泡
    2. cancelable:表示是否可以取消事件的默认行为
    3. currentTarget:表示事件处理程序当前正在处理事件的那个元素
    4. defaultPrevented:表示是否调用了preventDefault()
    5. detail:表示的是与事件相关的细节信息
    6. eventPhase:调用事件处理处理程序的阶段:1表示捕获阶段、2表示处于目标、3表示冒泡阶段

      在其中还有一些其他信息,在此就不再一一列举了。

    6、事件方面性能优化

      谈一谈事件方面如何优化性能——事件委托和事件处理程序的移除

      在JavaScript代码当中,添加到页面中的事件越多,页面的性能也就越差。导致这一问题的原因主要有:

    1. 每个函数都是对象,都会占用内存。内存中对象越多,性能也就越差。
    2. 必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。

      为了进行页面的性能优化,因此我们会采用两种方法,就是上面提到的——事件委托和事件处理程序的移除。

    6.1事件委托

      简单来说,当一个页面事件处理程序比较多的时候,我们会使用事件委托。

      事件委托主要利用了事件冒泡,只指定一个事件处理程序,就可以管理一个类型的所有事件。例如:我们为整个一个页面制定一个onclick事件处理程序,此时我们不必为页面中每个可点击的元素单独设置事件处理程序(onclick),效果:点击不同的元素执行不同的操作。

      还是,看一个例子。

         

    不使用事件委托:
    <body>
        <div class='control'>
            <span id='left'></span>
            <span id='first'>1</span>
            <span id='second'>2</span>
            <span id='third'>3</span>
            <span id='right'></span>
        </div>
    </body>
    <script>
        var left = document.getElementById('left');
        var first = document.getElementById('first');
        var second = document.getElementById('second');
        var third = document.getElementById('third');
        var right = document.getElementById('right');
        left.addEventListener("click", function(){
            alert('点击的是左这个字,执行相关操作');        
        }, false);
        first.addEventListener("click", function(){
            alert('要执行第一个序号对应的相关操作');        
        }, false);
        second.addEventListener("click", function(){
            alert('要执行第二个序号对应的相关操作');        
        }, false);
        third.addEventListener("click", function(){
            alert('要执行第三个序号对应的相关操作');        
        }, false);
        right.addEventListener("click", function(){
            alert('点击的是右这个字,执行相关操作');        
        }, false);
    </script>

      不难看出,我们使用了5个事件侦听器,每设置一个就需要绑定一个。

    使用事件委托:
    <body>
        <div class='control' id='control'>
            <span id='left'></span>
            <span id='first'>1</span>
            <span id='second'>2</span>
            <span id='third'>3</span>
            <span id='right'></span>
        </div>
    </body>
    <script>
        var control = document.getElementById('control');
        control.addEventListener("click", function(e){
            // 注意,此处没有考虑IE8-的浏览器,如果想书写兼容全部浏览器的事件处理程序,请查看下一篇博文
            var target = e.target;
            // 在此使用的是switch语句,也可以采用if进行判断
            switch(target.id){
                case "left" : {
                    alert('点击的是左这个字,执行相关操作');
                    break;
                }
                case "first" : {
                    alert('要执行第一个序号对应的相关操作');
                    break;
                }
                case "second" : {
                    alert('要执行第二个序号对应的相关操作');
                    break;
                }
                case "third" : {
                    alert('要执行第三个序号对应的相关操作');
                    break;
                }
                case "right" : {
                    alert('点击的是右这个字,执行相关操作');
                    break;
                }
            }
        }, false);
    </script>

      简要的总结一下所谓的事件委托:给元素的父级或者祖级,甚至页面绑定事件,然后利用事件冒泡的基本原理,通过事件目标对象进行检测,然后执行相关操作。其优势在于:

    1. 大大减少了事件处理程序的数量,在页面中设置事件处理程序的时间就更少了(DOM引用减少——也就是上面我们通过id去获取标签,所需要的查找操作以及DOM引用也就更少了)。
    2. document(注:上面的例子没有绑定在document上,而是绑定到了父级的div上,最为推荐的是绑定在document上)对象可以很快的访问到,而且可以在页面生命周期的任何时点上为它添加事件处理程序,并不需要等待DOMContentLoaded或者load事件。换句话说,只要可单击的元素在页面中呈现出来了,那么它就立刻具备了相应的功能。
    3. 整个页面占用的内存空间会更少,从而提升了整体的性能。

    6.2移除事件处理程序

      每当将一个事件处理程序指定给一个元素时,在运行中的浏览器代码与支持页面交互的JavaScript代码之间就会建立一个连接。连接数量也直接影响着页面的执行速度。所以,当内存中存在着过时的“空事件处理程序”的时候,就会造成Web应用程序的内存和性能问题。

      那么什么时候会造成“空事件处理程序”的出现呢?

    1. 文档中元素存在事件,通过一些DOM节点操作(removeChild、replaceChild等方法),移除了这个元素,但是DOM节点的事件没有被移除。
    2. innerHTML去替换页面中的某一部分,页面中原来的部分存在事件,没有移除。
    3. 页面卸载引起的事件处理程序在内存中的滞留。

      解决方法:

    1. 合理利用事件委托;
    2. 在执行相关操作的时候,先移除掉事件,再移除DOM节点;
    3. 在页面卸载之前,先通过onunload事件移除掉所有事件处理程序。

    7.创建一个兼容全部浏览器的事件侦听器

    7.1事件兼容

    1. 事件绑定:addEventListener 与 attachEvent(IE)
    2. 事件移除:removeEventListener 与 detachEvent(IE)
    3. 获取事件对象:e.target 与  window.event.srcElement(IE)
    4. 阻止冒泡:e.stopPropagation 与 window.event.cancelBubble(IE)
    5. 阻止默认:e.preventDefault 与 window.event.returnValue(IE)

    7.2具体的实现代码

    <!doctype html>
    <html>
    <head>
        <title>跨浏览器的事件处理函数——处理DOM2级事件兼容——独行冰海</title>
        <meta charset='utf-8' />
    </head>
    <body>
        <p id='con'>内容<a href="" id='def'>独行冰海-跨浏览器的事件处理函数——处理DOM2级事件兼容</a></p>
        <script type="text/javascript">
            /*调试*/
            addEventHandler(def,'click',preDefault);
            addEventHandler(def,'click',stopBubble);
            
            /*类库*/
            /*事件绑定*/
            function addEventHandler(obj,eventName,handler) {
                if (document.attachEvent) {
                    obj.attachEvent('on'+eventName,handler);
                }else if (document.addEventListener) {
                    obj.addEventListener(eventName,handler,false);
                }
            }
            /*事件移除*/
            function removeEventHandler(obj,eventName,handler) {
                if (document.detachEvent) {
                    obj.detachEvent('on'+eventName,handler);
                }else if (document.removeEventListener) {
                    obj.removeEventListener(eventName,handler,false);
                }            
            }
            /*获取事件对象*/
            function eventTarget(evt) {
                var evt = evt||window.event;
                var targetElement = evt.target||evt.srcElement;
                return targetElement;
            }
            /*阻止冒泡*/
            function stopBubble(evt) {
                var evt = evt||window.event;
                if (evt.stopPropagation) {
                    evt.stopPropagation();
                }
                else {
                    window.event.cancelBubble = true;
                }
            }
            /*阻止默认*/
            function preDefault(evt){
                var evt=event||window.event;
                if (evt&&evt.preventDefault) {
                    evt.preventDefault();
                }
                else {
                    window.event.returnValue = false;
                }
            }
        </script>
    </body>
    </html>
  • 相关阅读:
    新男人八题---AStringGame
    hihocoder1457
    SPOJ
    后缀自动机
    牛客练习赛13D
    Educational Codeforces Round 38
    Binary Differences
    laravel 带条件的分页查询
    url添加时间戳
    安卓无法上传照片
  • 原文地址:https://www.cnblogs.com/cjlalala/p/5802897.html
Copyright © 2020-2023  润新知