zoukankan      html  css  js  c++  java
  • 【JS】395-重温基础:事件

    640?wx_fmt=png

    本文是 重温基础 系列文章的第二十篇。

    这是第三个基础系列的第一篇,欢迎持续关注呀!重温基础 系列的【初级】和【中级】的文章,已经统一整理到我的【Cute-JavaScript】(http://js.pingan8787.com)的JavaScript基础系列中。

    本章节复习的是JS中的事件,事件冒泡捕获代理模拟等等。

    前置知识:事件来实现的,是文档或浏览器窗口中发生的一些特定的交互瞬间。

    1.事件流

    事件流描述的是从页面中接收事件的顺序,通常有这样两种完全相反的事件流概念:事件冒泡流(IE团队提出)和事件捕获流(网景团队提出)。

    1.1 事件冒泡

    冒泡事件(Event Bubbling):事件开始时由最具体的元素接收(文档中嵌套层次最深的那个节点),然后逐层向上传播到较为不具体的节点(文档),看下示例代码:

    <!DOCTYPE html>	
    <html>	
    <head>	
        <title>leo 事件冒泡</title>	
    </head>	
    <body>	
        <div id="leo">点击</div>	
    </body>	
    </html>

    点击页面中 <div>元素,这个 click事件就会按照下面顺序传播:

    1. <div>

    2. <body>

    3. <html>

    4. document

    由此可见,元素绑定的事件会通过DOM树向上传播,每层节点都会发生,直到 document对象,如图展示了冒泡过程:640?wx_fmt=png

    1.2 事件捕获

    事件捕获(Event Capturing):让不太具体的节点更早接收事件,而最具体的节点最后接收事件,即在事件到达预定目标之前捕获到,看下示例代码(HTML代码和前面一样),事件捕获的过程是这样的:

    1. document

    2. <html>

    3. <body>

    4. <div>

    看得出, document对象最新接收事件,然后沿DOM树依次向下,直到最后的实际目标 <div>元素,如图展示了捕获过程:

    640?wx_fmt=png

    注意:由于老版本的浏览器不支持,因此很少人使用事件捕获,不过如果特殊需求还是可以使用事件捕获,建议还是使用事件冒泡。

    1.3 DOM事件流

    “DOM2级事件”规定的事件流包含三个阶段:事件捕获阶段处于目标阶段事件冒泡阶段

    640?wx_fmt=png

    在DOM事件流中,实际目标( <div>元素)在捕获阶段不接收事件,即在捕获阶段,事件从 document对象到 <html>再到 <body>后就停止,进入“处于目标”阶段,事件在 <div>元素上发生,然后才进入冒泡阶段,将事件传回给文档。

    注意:目前主流浏览器都支持DOM事件流,只有IE8和之前版本不支持。

    2.事件处理

    事件处理,即响应某个事件。我们把事件处理的函数,称为“事件处理程序”。on开头,如 click事件的事件处理程序就是 onclick, load事件的事件处理程序就是 onload

    • HTML事件处理程序

    • DOM0级事件处理程序

    • DOM2级事件处理程序

    • IE事件处理程序

    • 跨浏览器事件处理程序

    2.1 HTML事件处理程序

    某个元素支持的事件,都可以用一个与相应事件处理程序同名的HTML特性来指定,这个特性的值应该是能够执行的JavaScript代码。比如:

    <input type="button" value="点击" οnclick="alert('hello leo');">

    也可以把需要执行的具体事件单独定义出来,可以放置与单独 .js文件,也可以在文档内用 <script>标签引入:

    function fun(){	
        alert('hello leo');	
    }
    <input type="button" value="点击" οnclick="fun()">

    我们通过这样指定事件处理程序,可以有一个局部变量 event来获取事件对象本身,在这个函数内部, this值等于这个变量 event

    <input type="button" value="点击" οnclick="fun(event)">

    另外,HTML中指定事件处理程序,会有2个缺点:

    1. 存在时间差

    2. 作用域链的异常 由于不同浏览器JavaScript引擎遵循的标识符解析规则存在差异,导致访问非限定对象成员时出错,表现为事件处理程序的作用域链在不同浏览器结果不同。

    3. HTML和JavaScript代码紧密耦合 这常常就是很多开发人员放弃HTML事件处理程序的原因。

    2.2 DOM0级事件处理程序

    通过赋值形式,将一个函数赋值给一个事件处理程序属性。每个元素(包含 window和 document)都有自己的事件处理属性,这些属性通常全部小写,如 onclick,将这种属性的值设置成一个函数,就可以指定事件处理程序:

    var leo = document.getElementById('leo');	
    leo.onclick = function(){	
        alert('hello leo!');	
    }

    使用DOM0级方法指定事件处理程序,被认为是元素的方法。此时的事件处理程序是在元素的作用域执行,那么,this就引用当前元素,可以访问元素的任何属性和方法:

    var leo = document.getElementById('leo');	
    leo.onclick = function(){	
        alert(this.id);  // "leo"	
    }

    我们也可以通过设置事件处理程序属性来删除DOM0级的事件处理程序。

    leo.onclick = null;

    2.3 DOM2级事件处理程序

    有2个方法:

    • 添加事件处理程序: addEventListener()

    • 删除事件处理程序: removeEventListener()

    所有的DOM节点都包含这两个方法,并且它们都接收三个参数:

    • 处理的事件名称

    • 事件处理程序的函数

    • 布尔值(true:事件捕获阶段调用,false:事件冒泡阶段调用)

    var leo = document.getElementById('leo');	
    leo.addEventListener('click',function(){	
        alert(this.id);        // "leo"	
    },false);

    与DOM0级方法一样,这里的事件处理程序也会是在元素的作用域中执行,因此this也是指向元素,可以访问元素的任何属性和方法。

    使用DOM2级方法,可以添加多个事件处理程序,并按照添加顺序触发

    var leo = document.getElementById('leo');	
    leo.addEventListener('click',function(){	
        alert(this.id);       // "leo"	
    },false);	
    leo.addEventListener('click',function(){	
        alert('hello leo!');  // "hello leo!"	
    },false);

    注意:通过 addEventListener()添加的事件只能通过 removeEventListener()移除,并且两者传入的参数一致,这就意味着通过 addEventListener()添加的匿名函数不能被移除,看下面代码:

    var leo = document.getElementById('leo');	
    leo.addEventListener('click',function(){	
        alert(this.id);       // "leo"	
    },false);	
    // 没有效果	
    leo.removeEventListener('click',function(){	
        // do some thing	
    },false);

    没有效果是因为这两个方法传入的函数,是完全不同的,为了达到删除事件处理程序的效果,我们可以将处理函数单独定义出来:

    var leo = document.getElementById('leo');	
    var fun = function(){	
        alert(this.id);	
    }	
    leo.addEventListener('click',fun,false);	
    // 有效果	
    leo.removeEventListener('click',fun,false);

    2.4 IE事件处理程序

    IE实现两种方法: attachEvent()和 detachEvent()。这两个方法接收相同的两个参数:事件处理程序名称事件处理程序函数attachEvent()添加的事件处理程序会被添加到冒泡阶段。

    var leo = document.getElementById('leo');	
    leo.attachEvent('onclick',function(){	
        alert('hello leo');       // "leo"	
    },false);	
    // attachEvent也支持添加多个事件处理程序	
    leo.attachEvent('onclick',function(){	
        alert('hello world');       // "leo"	
    },false);

    注意:这里的第一个参数是 onclick而不是DOM的 addEventListener()的 click

    IE的attachEvent()和DOM0级方法区别

    • DOM0级方法,作用域在所属元素的作用域。

    • attachEvent(),作用域在全局作用域,即 this指向 window

    和DOM0级方法一样, detachEvent()只能移除使用 attachEvent()添加的方法,为了避免无法移除,也是需要将处理的函数单独定义出来:

    var leo = document.getElementById('leo');	
    var fun = function(){	
        alert(this.id);	
    }	
    leo.attachEvent('onclick',fun,false);	
    // 有效果	
    leo.detachEvent('onclick',fun,false);

    2.6 跨浏览器事件处理程序

    在做跨浏览器事件处理程序,我们可以有两种方式:

    1. 使用能够隔离浏览器差异的JavaScript库

    2. 单独手写一个处理方法

    我们单独受写一个处理方法也不难,只需关注好事件冒泡阶段,我们可以创建一个方法,区分使用DOM0级方法DOM2级方法IE方法即可,默认采用DOM0级方法

    var my_event = {	
        addMyEvent:function(element, type, handler){	
            if(element.addEventListener){	
                element.addEventListener(type, handler, false);	
            }else if(element.attachEvent){	
                element.attachEvent('on' + type, handler);	
            }else{	
                element['on' + type] = handler;	
            }	
        };	
        removeMyEvent:function(element, type, handler){	
            if(element.removeEventListener){	
                element.removeEventListener(type, handler, false);	
            }else if(element.detachEvent){	
                element.detachEvent('on' + type, handler);	
            }else{	
                element['on' + type] = null;	
            }	
        }	
    }

    3.事件对象

    当触发一个DOM上的事件时,都会产生一个事件对象 event,并作为参数传入事件处理程序,这个对象包含所有与事件相关的信息。包括导致事件的元素、事件类型等其他信息。

    3.1 DOM中的事件对象

    无论指定事件处理程序时使用什么方法(DOM0级方法或DOM2级方法),都会传入 event对象:

    var leo = document.getElementById('leo');	
    leo.onclick = function(event){	
        alert(event.type);  // 'click'	
    }	
    leo.addEventListener('click',function(event){	
        alert(event.type);  // 'click'	
    },false);

    event对象包含与创建它的特定事件相关的属性和方法,常常有如下成员:640?wx_fmt=png

    640?wx_fmt=png

    我们可以使用 event中的对象和方法:

    • 阻止事件的默认行为

    var leo = document.getElementById('leo');	
    leo.onclick = function(event){	
        // 只有当 event 中的 cancelable 属性为true的事件	
        event.preventDefault();	
    }
    • 立即停止事件在DOM的传播

    通过调用 event.stopPropagation()方法避免弹框出现两次。

    var leo = document.getElementById('leo');	
    leo.onclick = function(event){	
        alert('leo');	
        event.stopPropagation();	
    }	
    document.body.onclick = function(event){	
        alert('hello leo');	
    }

    3.2 IE中的事件对象

    访问IE中的事件对象 event,方法有多种,取决于事件处理程序的方法:

    • DOM0级方法,使用 window.event

    var leo = document.getElementById('leo');	
    leo.onclick = function(){	
        var event = window.event;	
        alert(event.type);   // 'click'	
    }
    • IE的 attachEvent方法, event作为参数传入(也可以使用 window.event

    var leo = document.getElementById('leo');	
    leo.attachEvent('onclick',function(event){	
        alert(event.type);   // 'click'	
    },false);

    3.3 跨浏览器的事件对象

    虽然DOM和IE中 event对象不同,但我们也可以和前面的 跨浏览器事件处理程序 处理一样,通过他们之间的区别,实现映射:

    var my_event = {	
        myAddFun : function(element, type, handler){	
            // do some thing	
        },	
        // 返回对event的引用	
        getMyEvent : function(event){	
            return event?event:window.event;	
        },	
        // 返回事件的目标	
        getMyTarget : function(event){	
            return event.target || event.srcElement;	
        },	
        // 取消事件的默认行为	
        preventDefault : function(event){	
            if(event.preventDefault){	
                event.preventDefault();	
            }else {	
                event.returnValue = false;	
            }	
        },	
        myRemoveFun : function(element, type, handler){	
            // do some thing	
        },	
        // 阻止事件流	
        stopPropagetion : function(event){	
            if(event.stopPropagetion){	
                event.stopPropagetion();	
            }else {	
                event.cancelBubble = true;	
            }	
        }	
    }	
    var leo = document.getElementById('leo');	
    leo.onclick = function(event){	
        alert('leo');	
        event = my_event(event);	
        my_event.stopPropagation(event);	
    }	
    leo.onclick = function(event){	
        alert('hello world');	
    }

    4.事件类型

    Web浏览器中的事件类型有很多,DOM3级事件规定有以下几类事件类型:

    • UI事件:当用户与页面上元素交互时触发;

    • 焦点事件:当元素失去或获取焦点时触发;

    • 鼠标事件:当用户通过鼠标在页面操作时触发;

    • 滚轮事件:当使用鼠标滚轮(或类似设备)时触发;

    • 文本事件:当在文档中输入文本时触发;

    • 键盘事件:当用户通过键盘操作时触发;

    • 合成事件:当为IME输入字符时触发;

    • 变动事件:当底层DOM结构变动时触发;

    具体每个方法的详细介绍,可以查看W3school HTML DOM Event 对象

    5.事件委托

    简单理解就是讲一个响应事件委托到另一个元素。事件委托利用事件冒泡,指定一个事件处理程序来管理某一类型的所有事件,比如我们通过一个函数来代替其他三个函数:

    <ul id="btn">	
        <li id="btn1">按钮1</li>	
        <li id="btn2">按钮2</li>	
        <li id="btn3">按钮3</li>	
    </ul>
    var btn1 = document.getElementById('btn1');	
    var btn2 = document.getElementById('btn2');	
    var btn3 = document.getElementById('btn3');	
    // my_event 在前面定义了	
    my_event.myAddFun(btn1, 'click', function(event){	
        alert('btn1点击');	
    });	
    my_event.myAddFun(btn2, 'click', function(event){	
        alert('btn2点击');	
    });	
    my_event.myAddFun(btn3, 'click', function(event){	
        alert('btn3点击');	
    });

    下面我们在DOM树层级更高的元素上添加一个事件处理函数,来做事件委托,我们这么重写这段代码:

    var btn = document.getElementById('btn');	
    my_event.myAddFun(btn, 'click', function(event){	
        event = my_event.getMyEvent(event);	
        var target = my_event.getMyTarget(event);	
        switch(target.id){	
            case "btn1":	
                alert('btn1点击');	
                break;	
            case "btn2":	
                alert('btn2点击');	
                break;	
            case "btn3":	
                alert('btn3点击');	
                break;	
        }	
    });

    最适合采用事件委托技术的事件包括: clickmousedownmouseupkeyupkeydownkeypress,虽然 mouseover和 mouseout事件也有冒泡,但因为不好处理它们并且经常需要重新计算元素位置。

    可以看出,事件委托有以下优点:

    • 减少内存消耗

    • 动态绑定事件

    6.事件模拟

    JavaScript的事件模拟主要用来在任意时刻触发特定事件。

    6.1 DOM中的事件模拟

    在 document对象上使用 createEvent()方法创建一个 event对象。createEvent()接收一个参数,即要创建的事件类型的字符串。

    • UIEvents : 一般化的UI事件(鼠标和键盘事件都继承自UI事件)(DOM3级中 UIEvent

    • MouseEvents : 一般化的鼠标事件(DOM3级中 MouseEvent

    • MutationEvents : 一般化的DOM滚动事件(DOM3级中 MutationEvent

    • HTMLEvents : 一般化的HTML事件(DOM3级中 HTMLEvent

    创建 event之后,我们需要使用 dispatchEvent()方法去触发这个事件,需要传入一个参数,参数是需要触发事件的event对象。

    所有支持事件的DOM节点都支持这个方法。事件触发之后,事件就能照样冒泡并引发响应事件处理程序的执行。

    6.1.1 模拟鼠标事件

    使用 createEvent()方法传入 MouseEvents创建一个鼠标事件,返回的对象有一个 initMouseEvent()方法,用于指定与该鼠标事件相关的信息,有15个参数:

    • type :字符串,表示触发的事件类型,如 click

    • bubble : 布尔值,表示是否冒泡,为了精确模拟鼠标事件,通常设置为true

    • cancelable :布尔值,表示是否可以取消,为了精确模拟鼠标事件,通常设置为true

    • view : 与事件关联的视图,基本都设置为 document.defaultView

    • detail : 整数,与事件有关的详细信息,基本设置为0

    • screenX : 整数,事件相对屏幕的X坐标

    • screenY : 整数,事件相对屏幕的Y坐标

    • clientX : 整数,事件相对视口的X坐标

    • clientY : 整数,事件相对视口的Y坐标

    • ctrlKey : 布尔值,表示是否按下Ctrl键,默认false

    • altKey : 布尔值,表示是否按下Alt键,默认false

    • shiftKey : 布尔值,表示是否按下Shift键,默认false

    • metaKey : 布尔值,表示是否按下Meta键,默认false

    • button : 整数,表示按下哪个鼠标键,默认0

    • relatedTarget : 对象,表示与事件相关的对象,只在 mouseover和 mouseout时使用

    案例:

    var btn = document.getElementById('btn');	
    var myEvent = document.createEvent('MouseEvents');	
    myEvent.initMouseEvent(	
        'click', true, true, document.defaultView, 	
        0, 0, 0, 0, 0,	
        false, false, false, false, 0, null	
    )	
    btn.dispatchEvent(myEvent);

    6.1.2 模拟键盘事件

    DOM3级规定,使用 createEvent()方法传入 KeyboardEvent创建一个键盘事件,返回的对象有一个 initKeyEvent()方法,有8个参数:

    • type :字符串,表示触发的事件类型,如 keydown

    • bubble : 布尔值,表示是否冒泡,为了精确模拟键盘事件,通常设置为true

    • cancelable :布尔值,表示是否可以取消,为了精确模拟键盘事件,通常设置为true

    • view : 与事件关联的视图,基本都设置为 document.defaultView

    • key : 整数,表示按下的键的键码

    • localtion : 整数,表示按下哪里的键,默认0表示主键盘,1表示左,2表示右,3表示数字键盘,4表示移动设备(即虚拟键盘),5表示手柄

    • modifiers : 字符串,空格分隔的修改件列表,如"shift"

    • repeat : 整数,在一行中按了多少次这个键keypress事件,因此只能用这个方式来模拟 keyupkeydown事件。

    模拟按住Shift和A键的案例:

    var btn = document.getElementById('btn');	
    var myEvent;	
    // 以DOM3级方式创建	
    if(document.implementation.hasFeature('KeyboardEvents', '3.0')){	
        myEvent = document.createEvent('KeyboardEvent');	
        myEvent.initKeyboardEvent(	
            'keydown', true, true, document.defaultView,	
            'a', 0, 'Shift', 0	
        );	
    }	
    btn.dispatchEvent(myEvent);

    6.1.3 模拟其他事件

    如模拟变动事件HTML事件

    • 模拟变动事件

    通过 createEvent()传入 MutationEvents参数创建,返回一个 initMutationEvent()方法,这个方法接收参数包括: typebubblescancelablerelatedNodepreValuenewValueattrNameattrChange,下面模拟一个案例:

    var btn = document.getElementById('btn');	
    var myEvent = document.createEvent('MutationEvents');	
    myEvent.initMutationEvent(	
        'DOMNodeInserted', true, false, someNode, '', '', '', 0	
    );	
    btn.dispatchEvent(myEvent);
    • 模拟HTML事件

    通过 createEvent()传入 HTMLEvents参数创建,返回一个 initEvent()方法,下面模拟一个案例:

    var btn = document.getElementById('btn');	
    var myEvent = document.createEvent('HTMLEvents');	
    myEvent.initEvent('focus', true, false);	
    btn.dispatchEvent(myEvent);

    6.1.4 自定义DOM事件

    通过 createEvent()传入 CustomEvent参数创建,返回一个 initCustomEvent()方法,有4个参数:

    • type :字符串,表示触发的事件类型,如 keydown

    • bubble : 布尔值,表示是否冒泡,为了精确模拟键盘事件,通常设置为true

    • cancelable :布尔值,表示是否可以取消,为了精确模拟键盘事件,通常设置为true

    • detail : 对象,任意值,保存在 event对象的 detail属性中

    案例:

    var btn = document.getElementById('btn');	
    var myEvent;	
    // my_event在前面定义 2.6 跨浏览器事件处理程序	
    my_event.addMyEvent(btn, 'myevent', function(event){	
        alert('btn detail ', event.detail);	
    });	
    my_event.addMyEvent(document, 'myevent', function(event){	
        alert('document detail ', event.detail);	
    });	
    // 以DOM3级方式穿件	
    if(document.implementation.hasFeature('CustomEvents', '3.0')){	
        myEvent = document.createEvent('CustomEvent');	
        myEvent.initCustomEvent(	
            'myevent', true, false, 'hello leo',	
        );	
        btn.dispatchEvent(myEvent);	
    }

    6.2 IE中的事件模拟

    IE8及之前的版本模拟事件和DOM中模拟思路相似:想创建 event对象再指定信息,最后触发。document.createEventObject()方法创建 event对象,并且不接收参数,返回一个通用 event对象,我们要做的就是给这个 event对象添加信息,最后在目标上调用 fireEvent()方法,并接受两个参数(事件处理程序的名称和 event对象)。fireEvent()方法会自动添加 srcElement和 type属性,我们需要手动添加其他属性,下面模拟一个click事件:

    var btn = document.getElementById('btn');	
    var myEvent = document.createEventObject();	
    myEvent.screenX = 100;	
    myEvent.screenY = 0;	
    myEvent.clientX = 100;	
    myEvent.clientY = 0;	
    myEvent.ctrlKey = false;	
    myEvent.altKey = false;	
    myEvent.shiftKey = false;	
    myEvent.button = 0;	
    btn.fireEvent('onclick', event);

    参考文章

    1. 《JavaScript高级程序设计》第13章 事件

    个人博客:http://www.pingan8787.com 微信公众号【前端自习课】和千万网友一起,每日清晨,享受一篇前端优秀文章。 目前已连续推送文章 600+ 天,愿每个人的初心都能一直坚持下去!
  • 相关阅读:
    Codevs 1038 一元三次方程求解 NOIP 2001(导数 牛顿迭代)
    Bzoj 3942: [Usaco2015 Feb]Censoring(kmp)
    Bzoj 1355: [Baltic2009]Radio Transmission(kmp)
    Bzoj 2242: [SDOI2011]计算器(BSGS)
    Cogs 1345. [ZJOI2013] K大数查询(树套树)
    Cogs 58. 延绵的山峰(st表)
    洛谷 P2251 质量检测(st表)
    洛谷 P3382 【模板】三分法(三分 二分)
    Hihocoder #1142 : 三分·三分求极值
    P1967 货车运输
  • 原文地址:https://www.cnblogs.com/pingan8787/p/11838040.html
Copyright © 2011-2022 走看看