JavaScript DOM(三)-DOM事件

来源:互联网 发布:网络女主播闪现 编辑:程序博客网 时间:2024/05/18 03:11

1.简介

本文将从事件流(捕获阶段、目标阶段、冒泡阶段)、事件处理程序(HTML级、DOM0级、DOM2级、简单的兼容示例)、事件委托、模拟事件(DOM中的模拟事件、IE中的模拟事件、自定义事件)、事件对象、事件类型这几个方面来简要的介绍DOM事件。

2.事件流

事件流描述的是从页面中接收事件的顺序。
–《JavaScript高级程序设计》

事件冒泡

IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
–《JavaScript高级程序设计》

事件捕获

Netscape Communicator团队提出的另一种事件流叫做事件捕获(event capturing)。事件捕获的思想是不太具体的节点应该更早的接收到事件,而最具体的节点应该最后接收到事件。
–《JavaScript高级程序设计》

DOM事件流

“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
–《JavaScript高级程序设计》

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

3.事件处理程序

事件就是用户或者浏览器自身执行的某种动作,而响应某个事件的函数就叫做事件处理程序(事件侦听器)。

事件处理程序主要分为三种,分别是HTML事件处理程序、DOM0级事件处理程序和DOM2级事件处理程序,下面将分别对这三种事件处理程序进行总结分析。

HTML事件处理程序

示例代码

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>HTML事件处理程序</title>    <script type="text/javascript">        function showMessage() {            console.log('wrapper', this, event);        }        function showBodyMessage() {            console.log('wrapper', this, event);        }        var i = 10;    </script></head><body onclick="showBodyMessage" id="body">    <div onclick="showMessage()" id="wrapper">        <div onclick="console.log('inner', this, event, i)" id="inner">            click me!        </div>    </div></body></html>

分析总结:
1. 添加监听函数
- 1.1 有两种方式用来添加监听函数,一是在 on- 属性后直接写代码,点击后会执行;二是在 on- 属性后写一个函数。
- 1.2 on- 属性的值是一个将要执行的代码,在将函数名添加到值的位置时,不要忘记加上圆括号。
2. 移除监听函数。
- 无法直接移除监听函数。
3. 是否可以添加多个事件处理函数
- 不可以添加多个事件处理函数。
4. 事件传播
- 在主流浏览器上都是:事件冒泡。
5. this 指向
- 直接在属性值里写代码,this指向是绑定事件的那个Element节点。
- 在值的位置写函数的情况,this指向是全局window对象。
6. event对象
- 直接在属性值里写代码,可以直接使用event对象。
- 在值的位置写函数的情况,可以在不传参的情况下直接使用,也可以在参数列表中将event对象传过去。
7. 其他
- 事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。
- 通过event变量,可以直接访问事件对象,而你不用自己定义,也不用从函数的参数列表中读取。
- HTML代码与JavaScript代码紧密耦合。所以一般不推荐这种写法。

DOM0级事件处理程序

示例代码

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Document</title>    <script>        window.onload = function() {            var wrapperEle = document.getElementById('wrapper');            var innerEle = document.getElementById('inner');            innerEle.onclick = function() {                console.log('inner', this, event);            }            wrapperEle.onclick = showMessage;            function showMessage() {                console.log('wrapper', this, event);            }            // 移除监听函数            // wrapperEle.onclick = null;        }    </script></head><body><div id="wrapper" onclick="console.log('HTML 事件处理程序')">    <div onclick="console.log('html inner click')" id="inner">        click me!    </div></div></body></html>

分析总结:
1. 添加监听函数
- 获取对象,将这个属性的值设置为一个函数,就可以指定一个事件处理函数。
2. 移除监听函数
- 在其之后,将 on- 属性的值设置为 null,即可移除事件处理程序。
3. 是否可以添加多个事件处理函数
- 可以添加多个事件处理程序,但是只有最后一个会被执行。
- 如果在该节点上既有HTML事件处理程序,又有DOM0级事件处理程序,HTML事件处理程序会被覆盖,不会被执行。
4. 事件传播
- 在主流浏览器上为:事件冒泡。
5. this指向
- 绑定事件的Element节点。
6. event对象
- 可以传过去,即在函数参数位置写上形参。
- 也可以不传形参直接使用,但在函数体内只能以event引用event对象。
7. 其他
- 如果一个节点上既有HTML级事件处理程序,又有DOM 0级事件处理程序,HTML级事件处理程序会被覆盖,不会执行。

DOM2 级事件处理程序

示例代码

<!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <title>DOM2级事件</title>    <script type="text/javascript">        window.onload = function () {            // 测试捕获冒泡阶段代码            (function () {                var phases = ['', '捕获阶段', '目标阶段', '冒泡阶段'];                var rootEle = document.getElementById('root');                var wrapperEle = document.getElementById('wrapper');                var innerEle = document.getElementById('inner');                // 冒泡                rootEle.addEventListener('click', function () {                    console.log('root click', '冒泡监听');                    console.log(this, event, phases[event.eventPhase]);                }, false);                wrapperEle.addEventListener('click', function (e) {                    console.log('wrapper click', '冒泡监听');                    console.log(this, e, phases[event.eventPhase]);                }, false);                innerEle.addEventListener('click', function () {                    console.log('inner click', '冒泡监听');                    console.log(this, event, phases[event.eventPhase]);                }, false);                // 捕获                rootEle.addEventListener('click', function () {                    console.log('root click', '捕获监听');                    console.log(this, event, phases[event.eventPhase]);                }, true);                wrapperEle.addEventListener('click', function () {                    console.log('wrapper click', '捕获监听');                    console.log(this, event, phases[event.eventPhase]);                }, true);                innerEle.addEventListener('click', function () {                    console.log('inner click', '捕获监听');                    console.log(this, event, phases[event.eventPhase]);                }, true);            })();            // 测试监听、移除监听代码            (function () {                var innerEle = document.getElementById('inner');                var click1 = function () {                    console.log(1);                };                var click2 = function () {                    console.log(2);                };                var click3 = function () {                    console.log(3);                };                innerEle.addEventListener('click', click1, false);                innerEle.addEventListener('click', click2, false);                innerEle.addEventListener('click', click3, false);                // 这种方式监听的事件是无法移除监听的。                innerEle.addEventListener('click', function () {                    console.log(4);                }, false);//                innerEle.removeEventListener('click', click2, false);                // 无法移除监听//                innerEle.removeEventListener('click', function () {//                    console.log(4);//                }, false);            })();        }    </script></head><body id="root"><div id="wrapper">    <div id="inner">        click me!    </div></div></body></html>

分析总结:
1. 添加监听函数
- 使用 addEventListener 函数
2. 移除监听函数
- 使用 removeEventListener 函数
- 参数列表要求与 addEventListener 完全一致
3. 是否可以添加多个事件处理函数
- 可以,按照添加顺序执行
4. 事件传播
- 当指定第三个参数为 true 时,按照捕获方式调用。
- 其他情况,为冒泡方式调用。
5. this指向
- 绑定事件的Element节点。
6. event对象
- 可以传过去,即在函数参数位置写上形参。
- 也可以不传形参直接使用,但在函数体内只能以event引用event对象。
7. 其他
- 一个常见错误,匿名函数无法移除,详见示例。
- Edge支持该事件处理程序。

IE事件处理程序(与DOM2级事件处理程序对应)

示例代码(该代码只能在ie浏览器下运行)

<!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <title>IE事件处理程序</title>    <script type="text/javascript">        window.onload = function () {            var rootEle = document.getElementById('root');            var wrapperEle = document.getElementById('wrapper');            var innerEle = document.getElementById('inner');            rootEle.attachEvent('onclick', function () {                console.log('root listener 1', this, event);            });            wrapperEle.attachEvent('onclick', function () {                console.log('wrapper listener 1', this, event);            });            innerEle.attachEvent('onclick', function () {                console.log('inner listener 1', this, event);            });            rootEle.attachEvent('onclick', function () {                console.log('root listener 2', this, event);            });            wrapperEle.attachEvent('onclick', function () {                console.log('wrapper listener 2', this, event);            });            innerEle.attachEvent('onclick', function () {                console.log('inner listener 2', this, event);            });            rootEle.attachEvent('onclick', function () {                console.log('root listener 3', this, event);            });            wrapperEle.attachEvent('onclick', function () {                console.log('wrapper listener 3', this, event);            });            innerEle.attachEvent('onclick', function () {                console.log('inner listener 3', this, event);            });        }    </script></head><body id="root"><div id="wrapper">    <div id="inner">        click me!    </div></div></body></html>

与DOM2级事件处理程序对应,ie也有两个方法分别对应添加和移除事件处理程序:
- attachEvent()
- detachEvent()

分析总结:
1. 添加监听函数
- 使用 attachEvent 函数
- 第一个参数为’on-‘,例如’onclick’,而不是DOM中的’click’。
- 接受两个参数。因为IE8及之前的版本只支持事件冒泡。
2. 移除监听函数
- 使用 detachEvent 函数
- 参数列表要求与 attachEvent 完全一致
3. 是否可以添加多个事件处理函数
- 可以,按照添加顺序相反的顺序执行。(IE8及以下)
- 可以,按照添加顺序执行。(IE9及以上,不包括Edge)
4. 事件传播
- 冒泡方式调用。
5. this指向
- 指向 window 对象
6. event对象
- 可以传过去,即在函数参数位置写上形参。
- 也可以不传形参直接使用,但在函数体内只能以event引用event对象。
7. 其他
- 同样的,匿名函数无法移除。
- 支持IE事件处理程序的浏览器有IE和Opera。
- Edge支持标准DOM2级事件处理程序,不支持IE事件处理程序。

跨浏览器的事件处理程序

1.《JavaScript高级程序设计》示例

var EventUtil = {    addHandler: 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;       }    },    removeHandler: 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;       }    }}

2.使用原型拓展

HTMLElement.prototype.addEvent = function(type, fn, capture) {    var el = this;    if (window.addEventListener) {        el.addEventListener(type, function(e) {            fn.call(el, e);        }, capture);    } else if (window.attachEvent) {        el.attachEvent("on" + type, function(e) {            fn.call(el, e);        });    } };

注:
示例2来源于张鑫旭大神的文章漫谈js自定义事件、DOM/伪DOM自定义事件

4.事件委托

在JavaScript中,添加到页面上的事件处理程序将直接影响到页面的整体运行性能。首先,每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪事件。
–《JavaScript高级程序设计》

对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
–《JavaScript高级程序设计》

使用场景:
在一个父节点下,有很多个子节点,需要为所有的子节点添加点击事件。一般情况下,我们会为所有的元素添加点击事件,就会有很多个重复的事件处理程序。现在我们使用事件委托,只需在DOM树中合适的节点上添加一个事件处理程序就可以了。

示例程序

<!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport"          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>事件委托</title>    <script type="text/javascript">        window.onload = function () {            var ulEle = document.getElementById('wrapper');            ulEle.onclick = function () {                console.log('ul DOM0');            };            ulEle.addEventListener('click', function (e) {                console.log(this, e.target, e.currentTarget);                // 目标节点                var target = e.target;                var dataID = target.getAttribute('data-id');                console.log(dataID);            }, false);        }    </script></head><body><ul id="wrapper">    <li data-id="a">aaaaaaaaa</li>    <li data-id="b">bbbbbbbbb</li>    <li data-id="c">ccccccccc</li>    <li data-id="d">ddddddddd</li>    <li data-id="e">eeeeeeeee</li></ul></body></html>

如果可行的话,也可以考虑为document对象添加一个对象处理程序,用以处理页面上发生的某种特定类型的事件。
优点如下所示:
- document对象很快就可以访问,而且可以在页面生命周期的任何时点上为它添加事件处理程序(无需等待DOMContentLoaded或load事件)。
- 在页面中设置事件处理程序所需的时间更少。
- 整个页面占用的内存空间更少,能够提升整体性能。
–《JavaScript高级程序设计》

个人而言,我比较推荐在合适的节点添加事件委托,除非有必要或者事件比较容易处理,否则不建议将所有事件都委托到document对象上。

事件委托的优点:
- 减少了声明的事件总数,减少了所需内存。
- 能够对添加事件处理程序之后再添加的节点进行处理,而不需要对后来添加的节点再次添加事件处理程序。

分析总结:
- this指向,指向事件绑定的节点,即父节点。

5.模拟事件

我们可以使用JavaScript在任意时刻来触发特定的事件,而此时的事件就如同浏览器创建的一样。

DOM中的事件模拟

第一步,使用document对象的createEvent方法创建event对象。该方法接受一个参数,即表示要创建的事件类型的字符串。
第二步,在创建了event对象之后,还需要使用与事件有关的信息对其进行初始化。不同类型的这个方法的名字也不相同,具体要取决于createEvent方法中使用的参数。
第三步,使用dispatchEvent方法触发事件,所有支持事件的DOM节点都支持这个方法。

IE中的事件模拟

第一步,使用document.createEventObject()方法可以在IE中创建event对象。但与DOM方式不同的是,这个方法不接受参数,结果会返回一个通用的event对象。
第二步,必须手工为这个对象添加必要的信息(没有方法来辅助完成这一步骤)。
第三步,就是在目标节点上调用fireEvent()方法,这个方法接受两个参数:事件处理名称和event对象。在调用fireEvent方法时,会自动为event对象添加SRCElement和type属相,其他属性都必须通过手工添加。
换句话说,模拟任何IE支持的事件都采用相同的模式。

自定义事件

DOM3级还定义了“自定义事件”。自定义事件不是由DOM原生触发的,它的目的是让开发人员创建自己的事件。
要创建新的自定义事件,可以调用createEvent(‘CustomEvent’)。返回的对象有一个名为initCustomEvent()的方法,接受如下四个参数。
- type (字符串):触发的事件类型。
- bubbles (布尔值):表示事件是否应该冒泡。
- cancelable (布尔值):表示事件是否可以取消。
- detail (对象):任意值,保存在event对象的detail属性中。

一个简单的示例

<!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport"          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>自定义DOM事件</title>    <script type="text/javascript">        window.onload = function () {            var event = document.createEvent('CustomEvent');            event.initCustomEvent('dbElementClick', true, false, '点击了两个子节点');            var outerEle = document.getElementById('outer');            var wrapperEle = document.getElementById('wrapper');            outerEle.addEventListener('dbElementClick', function (e) {                console.log('outer', e.detail);            }, false);            wrapperEle.addEventListener('dbElementClick', function (e) {                console.log('wrapper', e.detail);            }, false);            var firstEle;            outerEle.addEventListener('click', function (e) {                var target = e.target;                if (!firstEle) {                    firstEle = target                } else if (target !== firstEle) {                    outerEle.dispatchEvent(event);                }            }, false);        }    </script></head><body id="wrapper"><div id="outer">    <div id="click1">        click me!    </div>    <div id="click2">        click me!    </div></div></body></html>

关于模拟事件和自定义事件,更详细的信息请参考阮一峰老师的文章或者《JavaScript高级程序设计》这本书。

6.事件对象(event)

阻止事件默认行为

event.preventDefault()event.cancelable;event.defaultPrevented;

其中
- preventDefault() 用来阻止事件的默认行为。
- cancelable 表示事件是否允许被取消。
- defaultPrevented 表示是否调用过 preventDefault() 方法。
- 监听事件 return false 会起到和 preventDefault() 方法一致的效果。

阻止事件传播

event.stopPropagation()event.stopImmediatePropagation()

其中
- stopPropagation() 方法用于阻止事件在DOM上的传播,捕获或者冒泡阶段均可调用。
- stopImmediatePropagation() 有两种作用,其一该方法可以阻止事件在DOM上的传播,类似于 stopPropagation() 方法。其二,如果对一个节点定义了多个事件监听函数,那么事件监听函数将会按照顺序依次执行,使用该方法后,之后的事件处理程序都不会被触发。

事件节点

event.currentTargetevent.target

其中
- event.currentTarget 属性指向正在执行的监听函数绑定的节点。
- event.target 属性指向触发事件的节点,即事件最初发生的节点。

更多细节请参考阮一峰老师的文章事件模型或《JavaScript高级程序设计》

最后,给出这一小节的示例程序

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>DOM 事件</title>    <script type="text/javascript">        window.onload = function () {            // 阻止事件默认行为            (function () { // 请先注释该函数下面的语句                var open = document.getElementById('open');                open.addEventListener('click', function () {                    event.preventDefault();                }, true);            })();            // 阻止事件传播            (function () {                var wrapperEle = document.getElementById('wrapper');                var innerEle = document.getElementById('inner');                wrapperEle.addEventListener('click', function () { // 只有这个事件会被触发                    event.stopPropagation();                    console.log('wrapper 捕获');                }, true);                innerEle.addEventListener('click', function () {                    console.log('inner 捕获');                }, true);                wrapperEle.addEventListener('click', function () {                    console.log('wrapper 冒泡');                }, false);                innerEle.addEventListener('click', function () {                    console.log('inner 冒泡');                }, false);            })();            (function () {                var wrapperEle = document.getElementById('wrapper');                var innerEle = document.getElementById('inner');                wrapperEle.addEventListener('click', function () { // 这个事件会被触发                    console.log('wrapper 捕获1');                }, true);                wrapperEle.addEventListener('click', function () { // 这个事件会被触发                    event.stopImmediatePropagation();                    console.log('wrapper 捕获2');                }, true);                wrapperEle.addEventListener('click', function () {                    console.log('wrapper 捕获3');                }, true);                innerEle.addEventListener('click', function () {                    console.log('inner 捕获');                }, true);                wrapperEle.addEventListener('click', function () {                    console.log('wrapper 冒泡');                }, false);                innerEle.addEventListener('click', function () {                    console.log('inner 冒泡');                }, false);            })();        }    </script></head><body><div id="wrapper">    <div id="inner">        click me!        <a id="open" href="https://www.baidu.com/" onclick="return false;">百度</a>    </div></div></body></html>

7.事件类型

由于事件类型繁多,本文不再赘述,详细信息请参考阮一峰老师的文章事件种类或《JavaScript高级程序设计》

8.联系我

如果您有任何疑问或本文侵犯了您的著作权,请联系我。 mail to kylin