http://javascript.ruanyifeng.com/dom/event.html#toc6

事件模型

DOM 的事件操作(监听和触发) 都定义在 EventTarget 接口

Element 节点, document节点, window对象 都部署了这个接口. XMLHttpRequest、AudioNode、AudioContext等浏览器内置对象,也部署了这个接口。

该接口 就是三个方法:

  • addEventListener: 绑定事件的监听函数
  • removeEventListener:移除事件的监听函数
  • dispatchEvent: 触发事件

addEventListener()

在当前节点或对象上, 定义一个特定事件的监听函数.

// 使用格式
target.addEventListener(type, listener[, useCapture]);

// 实例
window.addEventListener('load', function () {...}, false);
request.addEventListener('readystatechange', function () {...}, false);

*addEventListener方法接受三个参数: • type:事件名称,大小写敏感。 • listener:监听函数。事件发生时,会调用该监听函数。 • useCapture:布尔值,表示监听函数是否在捕获阶段(capture)触发(参见后文《事件的传播》部分),默认为false(监听函数只在冒泡阶段被触发)。老式浏览器规定该参数必写,较新版本的浏览器允许该参数可选。为了保持兼容,建议总是写上该参数。 *  下面是一个例子。 function hello() { console.log(‘Hello world’); }

var button = document.getElementById('btn');
button.addEventListener('click', hello, false);

上面代码中,addEventListener方法为button元素节点,绑定click事件的监听函数hello,该函数只在冒泡阶段触发。 addEventListener方法可以为当前对象的同一个事件,添加多个监听函数。这些函数按照添加顺序触发,即先添加先触发。如果为同一个事件多次添加同一个监听函数,该函数只会执行一次,多余的添加将自动被去除(不必使用removeEventListener方法手动去除)。 function hello() { console.log(‘Hello world’); }

document.addEventListener('click', hello, false);
document.addEventListener('click', hello, false);

执行上面代码,点击文档只会输出一行Hello world。 如果希望向监听函数传递参数,可以用匿名函数包装一下监听函数。

unction print(x) {
  console.log(x);
}

var el = document.getElementById('div1');
el.addEventListener('click', function () { print('Hello'); }, false);

上面代码通过匿名函数,向监听函数print传递了一个参数。

removeEventListener()

removeEventListener方法用来移除addEventListener方法添加的事件监听函数。 div.addEventListener(‘click’, listener, false); div.removeEventListener(‘click’, listener, false);

removeEventListener方法的参数,与addEventListener方法完全一致。它的第一个参数“事件类型”,大小写敏感。 注意,removeEventListener方法移除的监听函数,必须与对应的addEventListener方法的参数完全一致,而且必须在同一个元素节点,否则无效。

dispatchEvent()

dispatchEvent方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true。

target.dispatchEvent(event)

dispatchEvent方法的参数是一个Event对象的实例。

para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event); 上面代码在当前节点触发了click事件。 如果dispatchEvent方法的参数为空,或者不是一个有效的事件对象,将报错。 下面代码根据dispatchEvent方法的返回值,判断事件是否被取消了。


var canceled = !cb.dispatchEvent(event);
  if (canceled) {
    console.log('事件取消');
  } else {
    console.log('事件未取消');
  }
}

2. 监听函数

监听函数(listener)是事件发生时,程序所要执行的函数。它是事件驱动编程模式的主要编程方式。 DOM提供三种方法,可以用来为事件绑定监听函数。

2.1 HTML标签的on-属性

HTML语言允许在元素标签的属性中,直接定义某些事件的监听代码。 <body onload="doSomething()"> <div onclick="console.log('触发事件')">

上面代码为body节点的load事件、div节点的click事件,指定了监听函数。 使用这个方法指定的监听函数,只会在冒泡阶段触发。 注意,使用这种方法时,on-属性的值是将会执行的代码,而不是一个函数。

<!-- 正确 -->
<body onload="doSomething()">

<!-- 错误 -->
<body onload="doSomething">

一旦指定的事件发生,on-属性的值是原样传入JavaScript引擎执行。因此如果要执行函数,不要忘记加上一对圆括号。

另外,Element元素节点的setAttribute方法,其实设置的也是这种效果。

el.setAttribute('onclick', 'doSomething()');

2.2 Element节点的事件属性

Element节点对象有事件属性,同样可以指定监听函数。

window.onload = doSomething;

div.onclick = function(event){
  console.log('触发事件');
};

使用这个方法指定的监听函数,只会在冒泡阶段触发。

2.3 addEventListener方法

通过Element节点、document节点、window对象的addEventListener方法,也可以定义事件的监听函数。

window.addEventListener('load', doSomething, false);

上面三种方法比较: 第一种“HTML标签的on-属性”,违反了HTML与JavaScript代码相分离的原则; 第二种“Element节点的事件属性”的缺点是,同一个事件只能定义一个监听函数,也就是说,如果定义两次onclick属性,后一次定义会覆盖前一次。因此,这两种方法都不推荐使用,除非是为了程序的兼容问题,因为所有浏览器都支持这两种方法。

addEventListener是推荐的指定监听函数的方法。 它有如下优点: • 可以针对同一个事件,添加多个监听函数。 • 能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发回监听函数。 • 除了DOM节点,还可以部署在window、XMLHttpRequest等对象上面,等于统一了整个JavaScript的监听函数接口。

2.4 this 对象的指向

实际编程中,监听函数内部的this对象,常常需要指向触发事件的那个Element节点。

addEventListener方法指定的监听函数,内部的this对象总是指向触发事件的那个节点。

// HTML代码为
// <p id="para">Hello</p>

var id = 'doc';
var para = document.getElementById('para');

function hello(){
  console.log(this.id);
}

para.addEventListener('click', hello, false);

执行上面代码,点击<p>节点会输出para。这是因为监听函数被“拷贝”成了节点的一个属性,所以this指向节点对象。使用下面的写法,会看得更清楚。

para.onclick = hello;

如果将监听函数部署在Element节点的on-属性上面,this不会指向触发事件的元素节点。

<p id="para" onclick="hello()">Hello</p>
<!-- 或者使用JavaScript代码  -->
<script>
  pElement.setAttribute('onclick', 'hello()');
</script>

执行上面代码,点击<p>节点会输出doc。这是因为这里只是调用hello函数,而hello函数实际是在全局作用域执行,相当于下面的代码。 para.onclick = function () { hello(); }

一种解决方法是,不引入函数作用域,直接在on-属性写入所要执行的代码。因为on-属性是在当前节点上执行的。 <p id="para" onclick="console.log(id)">Hello</p> <p id="para" onclick="console.log(this.id)">Hello</p>

上面两行,最后输出的都是para。 总结一下,以下写法的this对象都指向Element节点。

// JavaScript代码
element.onclick = print
element.addEventListener('click', print, false)
element.onclick = function () {console.log(this.id);}

// HTML代码
<element onclick="console.log(this.id)">

以下写法的this对象,都指向全局对象 // JavaScript代码 element.onclick = function (){ doSomething() }; element.setAttribute(‘onclick’, ‘doSomething()’);

// HTML代码
<element onclick="doSomething()">

3 事件的传播

3.1 传播的三个阶段

一个事件发生后,它会在不同的DOM节点之间传播(propagation)。

第一阶段:“捕获阶段”(capture phase) 从window对象传导到目标节点

第二阶段:“目标阶段”(target phase) 在目标节点上触发,

第三阶段:“冒泡阶段”(bubbling phase) 从目标节点传导回window对象

这种三阶段的传播模型,会使得一个事件在多个节点上触发。

3.2 事件的代理

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)。

4. Event 对象

事件发生以后,会生成一个事件对象,作为参数传给监听函数。 浏览器原生提供一个Event对象,所有的事件都是这个对象的实例,或者说继承了Event.prototype对象。

Event对象本身就是一个构造函数,可以用来生成新的实例。

event = new Event(typeArg, eventInit);

4.1 event.bubbles,event.eventPhase

(1)bubbles bubbles属性返回一个布尔值,表示当前事件是否会冒泡。 function goInput(e) { if (!e.bubbles) { passItOn(e); } else { doOutput(e); } }

(2)event.eventPhase eventPhase属性返回一个整数值,表示事件目前所处的节点。 var phase = event.eventPhase;

• 0,事件目前没有发生。 • 1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。该过程是从Window对象到Document节点,再到HTMLHtmlElement节点,直到目标节点的父节点为止。 • 2,事件到达目标节点,即target属性指向的那个节点。 • 3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。该过程是从父节点一直到Window对象。只有bubbles属性为true时,这个阶段才可能发生。

4.2 event.cancelable,event.defaultPrevented

1)cancelable cancelable属性返回一个布尔值,表示事件是否可以取消。

(2)defaultPrevented defaultPrevented属性返回一个布尔值,表示该事件是否调用过preventDefault方法。

4.3 event.currentTarget,event.target

addEventListener

菜鸟教程 addEventListener() 方法用于向指定元素添加事件句柄。 

为 <button> 元素添加点击事件。 当用户点击按钮时,在 id=”demo” 的 <p> 元素上输出 “Hello World” : document.getElementById(“myBtn”).addEventListener(“click”, function(){ document.getElementById(“demo”).innerHTML = “Hello World”; });

语法

element.addEventListener(event, function, useCapture)

  • event: 必须。字符串,指定事件名。 注意: 不要使用 “on” 前缀。 例如,使用 “click” ,而不是使用 “onclick”。

  • function: 必须。指定要事件触发时执行的函数。 当事件对象会作为第一个参数传入函数。 事件对象的类型取决于特定的事件。例如, “click” 事件属于 MouseEvent(鼠标事件) 对象。

  • useCapture: 可选。布尔值,指定事件是否在捕获或冒泡阶段执行。 可能值: true - 事件句柄在捕获阶段执行 false- 默认。事件句柄在冒泡阶段执行

您可以在同一个元素中添加不同类型的事件。 该实例演示了如何在同一个 <button> 元素中添加多个事件: document.getElementById(“myBtn”).addEventListener(“mouseover”, myFunction); document.getElementById(“myBtn”).addEventListener(“click”, someOtherFunction); document.getElementById(“myBtn”).addEventListener(“mouseout”, someOtherFunction);