本章内容
- 理解事件流
- 使用事件处理程序
- 不同的事件类型
JavaScript 与 HTML 之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。
13.1 事件流
事件流描述的是从页面中接收事件的顺序。IE 的事件流是事件冒泡流,而 Netscape Communicator 的事件流失事件捕获流。
13.1.1 事件冒泡
IE 的事件流叫做事件冒泡,即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点。以下面的 HTML 页面为例:
event bubbling example
click me
如果单击了页面中的 所有现代浏览器都支持事件冒泡,但在具体实现上还是有一些差别。 事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。前述例子会按下列顺序触发 虽然事件捕获是 Netscape Communicator 唯一支持的事件流模型,但 其他主流浏览器目前也都支持这种事件流模型。 “DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发送的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。 事件就是用户或浏览器自身执行的某种动作。诸如 某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的 HTML 特性来指定。这个特性的值应该是能够执行的 JavaScript 代码。例如: 在 HTML 中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本,如下例所示: 事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。 通过 JavaScript 指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代 Web 浏览器中出现的,而且至今仍然为所有现代浏览器所支持。原因一是简单,二是具有跨浏览器的优势。要使用 JavaScript 指定事件处理程序,首先必须取得一个要操作的对象的引用。 使用 DOM0 级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的 以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。 “DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作: 与 DOM0 级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行。使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。如下例: 这里为按钮添加了俩事件处理程序。这两个事件处理程序会按照添加它们的顺序触发,因此首先会显示元素的 ID,其次会显示“ 再看看下例: 大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度地兼容各种浏览器。 IE 实现了与 DOM 中类似的两个方法: 注意, 与 DOM 方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。 为了以跨浏览器的方式处理事件,不少开发人员会使用能够隔离浏览器差异的 JavaScript 库,还有一些会自己开发最合适的事件处理的方法。自己编写代码其实也不难,只要恰当地使用能力检测即可。要保证处理事件的代码能在大多数浏览器下一致地运行,只需关注冒泡阶段。 可以像下面这样使用 在触发 DOM 上的某个事件时,会产生一个事件对象 兼容 DOM 的浏览器会将一个 在事件处理程序内部,对象 如果事件处理程序存在于按钮的父节点中,那么这些值是不相同的。 在需要通过一个函数处理多个事件时,可以使用 要阻止特定事件的默认行为,可以使用 只有 事件对象的 只有在事件处理程序执行期间, 要访问 IE 中的 IE 中 “DOM3级事件”规定了一下几类事件。 焦点事件会在页面获得或失去焦点时触发。利用这些事件并与 1.键码 对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡。 在使用事件时,需要考虑如下一些内存与性能方面的问题。click
事件就会按照如下顺序传播:
document
13.1.2 事件捕获
click
事件。
document
建议放心地使用事件冒泡,在有特殊需要时再使用事件捕获。13.1.3 DOM 事件流
在 DOM 事件流中,实际的目标(document
到再到
后就停止了。下一个阶段是“处于目标”阶段,于是事件在
多数支持 DOM 事件流的浏览器都实现了一种特定的行为:即使“DOM2 级事件”规范明确要求捕获阶段不会涉及事件目标,但高版本浏览器都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会再目标对象上面操作事件。
13.2 事件处理程序
click
、load
和mouseover
,都是事件的名字。而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以“on”开头,因此click
事件的事件处理程序就是onclick
,load
事件的事件处理程序就是onload
。为事件指定处理程序的方式有好几种。13.2.1 HTML 事件处理程序
这样指定事件处理程序具有一些独到之处。首先,这样会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event
,也就是事件对象。
不过,在 HTML 中指定事件处理程序有两个缺点。首先,存在一个时差问题。因为用户可能会在 HTML 元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。
另一个缺点是,这样扩展事件处理程序的作用域在不同浏览器中会导致不同结果。
最后一个缺点是 HTML 与 JavaScript 代码紧密耦合。故推荐使用 JavaScript 指定事件处理程序。13.2.2 DOM0 级事件处理程序
每个元素都有自己的事件处理程序属性,这些属性通常全部小写,例如onclick
。将这种属性的值设置为一个函数,就可以指定事件处理程序,如下所示:var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert('clicked');
};
this
引用当前元素。如下例:var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert(this.id); //"myBtn"
};
可以删除通过 DOM0 级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设置为null
即可。btn.onclick = null; //删除事件处理程序
13.2.3 DOM2 级事件处理程序
addEventListener()
和removeEventListener()
。所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true
,表示在捕获阶段调用事件处理程序;如果是false
,表示在冒泡阶段调用事件处理程序。
要在按钮上为click
事件添加事件处理程序,可以使用下列代码:var btn = document.getElementById("myBtn");
btn.addEventListener('click', function () {
alert(this.id);
}, false);
var btn = document.getElementById('myBtn');
btn.addEventListener("click", function () {
alert(this.id);
}, false);
btn.addEventListener("click", function () {
alert("Hello world!");
}, false);
Hello world!
”消息。
通过addEventListener()
添加的事件处理程序只能使用removeEventListener()
来移除;移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()
添加的匿名函数将无法移除,如下例:var btn = document.getElementById("myBtn");
btn.addEventListener("click", function () {
alert(this.id);
}, false);
//这里省略了其他代码
btn.removeEventListener("click", function() {
alert(this.id); //没有用
}, false)
var btn = document.getElementById("myBtn");
var handler = function () {
alert(this.id);
};
btn.addEventListener("click", handler, false);
//这里省略了其他代码
btn.removeEventListener("click", handler, false); //有效!
13.2.4 IE 事件处理程序
attachEvent()
和detachEvent()
。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于 IE8 及更早版本只支持事件冒泡,所以通过attachEvent()
添加的事件处理程序都会被添加到冒泡阶段。
要使用attachEvent()
为按钮添加一个事件处理程序,可以使用以下代码。var btn = document.getElementById('myBtn');
btn.attachEvent('onclick', function() {
alert("clicked");
});
attachEvent()
的第一个参数是"onclick"
。
在 IE 中使用attachEvent()
与使用 DOM0 级方法的主要区别在于事件处理程序的作用域。在使用 DOM0 级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()
方法的情况下,事件,事件处理程序会在全局作用域中运行,因此this
等于window
。看下例:var btn = document.getElementById('myBtn');
btn.attachEvent("onclick", function () {
alert(this === window); //true
});
var btn = document.getElementById('myBtn');
btn.attachEvent("onclick", function () {
alert("clicked");
});
btn.attachEvent("onclick", function () {
alert("Hello world!");
});
使用attachEvent()
添加的事件可以通过detachEvent()
来移除。var btn = document.getElementById("myBtn");
var handler = function () {
alert("clicked");
};
btn.attachEvent("onclick", handler);
//这里省略了其他代码
btn.detachEvent("onclick", handler);
13.2.5 跨浏览器的事件处理程序
第一个要创建的方法是addHandler()
,它的职责是视情况分别使用 DOM0 级方法、DOM2 级方法或 IE 方法来添加事件。这个方法属于一个名叫EventUtil
的对象,本书将使用这个对象来处理浏览器间的差异。addHandler()
方法接受 3 个参数:要操作的元素、事件名称和事件处理程序。
与addHandler()
对应的方法是removeHandler()
,它也接受相同的参数。这个方法的职责是移除之前添加的事件处理程序,默认采用 DOM0 级方法。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;
}
}
EventUtil
对象:var btn = document.getElementById("myBtn");
var handler = function () {
alert("clicked");
};
EventUtil.addHandler(btn, "click", handler);
//这里省略了其他代码
EventUtil.removeHandler(btn, "click", handler);
addHandler()
和removeHandler()
没有考虑到所有的浏览器问题,例如在 IE 中的作用域问题。13.3 事件对象
event
,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型以及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,而键盘操作导致的事件对象中,会包含与按下的键有关的信息。所有浏览器都支持event
对象,但支持方式不同。13.3.1 DOM 中的事件对象
event
对象传入到事件处理程序中。var btn = document.getElementById("myBtn");
btn.onclick = function (event) {
alert(event.type); //"click"
};
btn.addEventListener('click', function(event) {
alert(event.type); //"click"
}, false);
this
始终等于currentTarget
的值,而target
则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this
、currentTarget
和target
包含相同的值。var btn = document.getElementById('myBtn');
btn.onclick = function(event) {
alert(event.currentTarget === this); //true
alert(event.target === this); //true
};
document.body.onclick = function(event) {
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById('myBtn')); //true
}
type
属性。var btn = document.getElementById('myBtn');
var handler = function(event) {
switch(event.type) {
case "click":
alert('clicked');
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
preventDefault()
方法。例如:链接的默认行为就是在被单机时会导航到其href
特性指定的 URL。若想要阻止链接导航这一默认行为,那么通过链接的onclick
事件处理程序可以取消它。var link = document.getElementById('myLink');
link.onclick = function(event) {
event.preventDefault();
};
cancelable
属性设置为true
的事件,才可以使用preventDefault()
来取消其默认行为。
另外,stopPropagation()
方法用于立即停止事件在 DOM 层次中的传播,即取消进一步的事件捕捉或冒泡。例如:var btn = document.getElementById('myBtn');
btn.onclick = function(event) {
alert('Clicked');
event.stopPropagation();
};
document.body.onclick = function(event) {
alert('body clicked');
};
eventPhase
属性,可以用来确定事件当前正位于事件流的那个阶段。如果是在捕获阶段调用的事件处理程序,那么等于1
;如果事件处理程序处于目标对象上,则等于2
;如果是在冒泡阶段调用的事件处理程序,则等于3
。尽管“处于目标”发生在冒泡阶段,但eventPhase
仍然一直等于2
。var btn = document.getElementById('myBtn');
btn.onclick = function(event) {
alert(event.eventPhase); //2
};
document.body.addEventListener('click', function(event) {
alert(event.eventPhase); //1
}, true);
document.body.onclick = function(event) {
alert(event.eventPhase); //3
};
event
对象才会存在,一旦事件处理程序执行完成,event
对象就会被销毁。13.3.2 IE 中的事件对象
event
对象有几种不同的方式,取决于指定事件处理程序的方法。在使用 DOM0 级方法添加事件处理程序时,event
对象作为window
对象的一个属性存在。var btn = document.getElementById("myBtn");
btn.onclick = function () {
var event = window.event;
alert(event.type); //"click"
};
13.3.3 跨浏览器的事件对象
event
对象的全部信息和方法 DOM 对象中都有,只不过实现方式不一样。var EventUtil = {
addHandler: function(element, type, handler) {
//省略的代码
},
getEvent: function(event) {
return event ? event : window.event;
},
getTarget: function(event) {
return event.target || event.srcElement;
},
preventDefault: function (event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler) {
//省略的代码
},
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
btn.onclick = function(event) {
event = EventUtil.getEvent(event);
};
13.4 事件类型
13.4.1 UI 事件
var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");
var isSupported = document.implementation.hasFeature("UIEvent", "3.0");
当页面完全加载后(包括所有图像、JavaScript 文件、CSS 文件等外部资源),就会触发window
上面的load
事件。
图像上面也可以触发load
事件,无论是在 DOM 中的图像元素还是 HTML 中的图像元素。
这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload
事件。而利用这个事件最多的情况是清除引用,以避免内存泄漏。
当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize
事件。这个事件在window
上面触发,因此可以通过 JavaScript 或者元素中的
onresize
特性来指定事件处理程序。
虽然scroll
事件是在window
对象上发生的,但它实际表示的则是页面中相应元素的变化。13.4.2 焦点事件
document.hasFocus()
方法及document.activeElement
属性配合,可以知晓用户在页面上的行踪。13.4.3 鼠标与滚轮事件
13.4.4 键盘与文本事件
2.字符编码
3.DOM3 级变化
4.textInput
事件
5.设备中的键盘事件13.4.5 复合事件
13.4.6 变动事件
13.4.7 HTML5 事件
13.4.8 设备事件
13.4.9 触摸与手势事件
13.5 内存和性能
13.5.1 事件委托
13.5.2 移除事件处理程序
btn.onclick = function() {
btn.onclick = null;
document.getElementById("myDiv").innerHTML = "Processing...";
}
13.6 模拟事件
13.7 小结
可以使用 JavaScript 在浏览器中模拟事件。