您的位置:首页 > Web前端 > JavaScript

第十三章:事件类型(键盘和文本事件、复合事件、变动事件)

2017-05-26 10:53 337 查看

事件

事件类型

键盘和文本事件

用户在使用键盘时会触发键盘事件“DOM2级事件”最初也规定了键盘事件,但定稿前又删除了相应的内容。故,对键盘事件的支持主要遵循的是DOM0级

“DOM3级事件”为键盘事件制定了规范,IE9率先完全实现了该规范。但仍然有很多的遗留问题。

有3个键盘事件,简述如下:

keydown:当用户按下键盘上的任意键触发,如果按住不放,则会重复触发(mousedown没有这个特点)。

keypress:当用户按下键盘上的字符键简单来说就是会影响文本的键)触发,如果按住不放,则会重复触发。按下Esc键也会触发这个事件。Safari3.1之前的版本会在用户按下非字符键时触发keypress事件。

keyup:当用户释放键盘上的键时触发。

还有一个textInput事件。这个事件是对keypress的补充,用意是在将文本显示给用户之前更容易拦截文本(后面细说)。

在用户按了一下键盘上的字符键时,首先会触发keydown 事件,然后紧跟着是keypress 事件,最后会触发keyup 事件。其中,keydown 和keypress 都是在文本框发生变化之前被触发的;而keyup事件则是在文本框已经发生变化之后被触发的。如果用户按下了一个字符键不放,就会重复触发keydown 和keypress 事件,直到用户松开该键为止。

类似地,如果按下的是非字符键,则除了没有keypress这个阶段,其余都和上面一样。

键盘事件同样支持修改键。键盘事件的event对象中也有shiftKey、ctrlKey、altKey和metaKey(IE中没有该属性)属性,用法请看前一篇文章。

键码

在发生keydown 和keyupkeypress事件有所不同)事件时,event 对象的keyCode 属性中会包含一个代码,与键盘上一个特定的键对应。对数字字母字符键小键盘数字键属于非字符键),keyCode 属性的值与ASCII 码中对应小写字母或数字的编码相同。因此,数字键7 的keyCode 值为55,而字母A 键的keyCode 值为65——与Shift 键的状态无关。DOM 和IE 的event 对象都支持keyCode 属性。

<!DOCTYPE html>
<html>
<head>
<title>Keyup Event Example</title>
</head>
<body>
<input type="text" id="myText" />
<script type="text/javascript">
var textbox = document.getElementById("myText");
textbox.onkeyup = function(event){
alert(event.keyCode);//输入a或者A都是65
}
</script>
</body>
</html>


下面是所有非字符键的键码





字符编码

发生keypress事件意味着按下的键会影响到屏幕中文本的显示(虽然说小键盘也会影响屏幕中文本显示,但小键盘也有其他用法)。在所有浏览器中,按下能够插入或删除字符的键都会触发keypress 事件;按下其他键能否触发此事件因浏览器而异。

IE9、Firefox、Chrome 和Safari 的event 对象都支持一个charCode 属性,这个属性只有在发生keypress事件时才包含值。这个值一般代表按下的那个键的ASCII编码。且此时keyCode通常为0或者也可能等于所按键的编码。IE8 及之前版本和Opera 则是在keyCode 中保存字符的ASCII编码。

<!DOCTYPE html>
<html>
<head>
<title>Keypress Event Example</title>
</head>
<body>
<input type="text" id="myText" />
<script type="text/javascript">
function getCharCode(event){
if (typeof event.charCode == "number"){
return event.charCode;
} else {
return event.keyCode;
}
}
var textbox = document.getElementById("myText");
textbox.onkeypress =  function(event){
alert(String.fromCharCode(getCharCode(event)));
}
</script>
</body>
</html>


注意在这里是能够分辨大小写的。

DOM3级变化

DOM3级事件中的键盘事件,不再包含charCode 属性(浏览器为了兼容不会去掉这个属性),而是包含两个新属性:keychar

key属性是为了取代keyCode而新增的,它的值是一个字符串。在按下字符键时,key的值就是相应的文本字符。当按下非字符键时,key的值则是键的名称(如”Shift”和”Alt”)。而char属性在按下字符键时行为和key相同,在按下非字符键时则为null

书上说IE9 支持key 属性,但不支持char 属性(测试下来IE9支持char,也许我使用的版本比较新)。Safari 5 和Chrome 支持名为keyIdentifier 的属性(但我测试下来Chrome中的该属性已废弃),在按下非字符键(例如Shift)的情况下与key 的值相同。对于字符键,keyIdentifier 返回一个格式类似“U+0000”的字符串,表示Unicode 值(这部分是书上所说,由于已经废弃我就不再举例子了)。

个人见解,上面这些乱七八糟的属性和用法我觉得没必要太过于的深入了解。一般使用最通用的keyCode就够用了。

DOM3 级事件还添加了一个名为location 的属性,这是一个数值,表示按下了什么位置上的键:0 表示默认键盘1 表示左侧位置(例如左位的Alt 键),2 表示右侧位置(例如右侧的Shift 键),3 表示数字小键盘4 表示移动设备键盘(也就是虚拟键盘),5 表示手柄(如任天堂Wii 控制器)。IE9 支持这个属性[/b]。Safari 和Chrome 支持名为keyLocation 的等价属性(也被弃用了,应该是Chrome后期实现了location属性,所以后面的bug也没有什么测试的必要了),但即有bug——值始终是0,除非按下了数字键盘(此时,值 为3);否则,不会是1、2、4、5。有兴趣的读者可以试一试,反正我没什么兴趣。

最后是给event 对象添加了getModifierState()方法。这个方法接收一个参数,即等于Shift、Control、AltGraph 或Meta 的字符串,表示要检测的修改键。如果指定的修改键是活动的(也就是处于被按下的状态),这个方法返回true,否则返回false。(个人觉得没有什么卵用,要这样我干嘛不直接用shiftKey、ctrlKey、altKey和metaKey呢。且我使用alert(event.getModifierState(“Shift”));无论在IE还是Chrome中始终弹出false,所以还是别用了。

textInput事件

该事件是“DOM3级事件”规范引入的,意在替代keypress事件。它和keypress事件的区别有两个。一是只有可编辑区域才能触发该事件(例如input),二是textInput 事件只会在用户按下能够输入实际字符的键时才会被触发(退格不会触发)。由于textInput 事件主要考虑的是字符,因此它的event 对象中还包含一个data 属性(和之前的key属性类似),这个属性的值就是用户输入的字符(而非字符编码)。

当然如果是一个键一个键按,那么还不如直接用keypress。这个事件的特殊性在于它是针对文本变化而触发,而不是通过按键触发的。啥意思呢?就是说我通过脚本改变文本也会触发这个事件,我通过复制粘贴也会触发这个事件。只要改变了文本,都会触发这个事件。因此,DOM3级事件还定义了一个inputMethod属性,表示把文本输入到文本框中的方式:

0,表示浏览器不确定是怎么输入的。

1,表示是使用键盘输入的。

2,表示文本是粘贴进来的。

3,表示文本是拖放进来的。

4,表示文本是使用IME 输入的。

5,表示文本是通过在表单中选择某一项输入的。

6,表示文本是通过手写输入的(比如使用手写笔)。

7,表示文本是通过语音输入的。

8,表示文本是通过几种方法组合输入的。

9,表示文本是通过脚本输入的。

书上说支持textInput 属性的浏览器有IE9+、Safari 和Chrome。只有IE 支持inputMethod 属性。但是我发现IE9好像没用。所以我也没办法测试inputMethod的具体用法了。

设备中的键盘事件

这个部分是关于任天堂Wii遥控器的键盘事件。有需要的去看书P402页。

复合事件

复合事件是DOM3级事件中新添加的一类事件,用于处理IME的输入序列。IME(Input Method Editor,输入法编辑器)可以让用户输入在物理键盘上找不到的字符。有以下三种复合事件:

compositionstart:在IME 的文本复合系统打开时触发,表示要开始输入了。

compositionupdate:在向输入字段中插入新字符时触发。

compositionend:在IME 的文本复合系统关闭时触发,表示返回正常键盘输入状态。

复合事件与文本事件在很多方面都很相似。在触发复合事件时,目标是接收文本的输入字段。但它比文本事件的事件对象多一个属性data,其中包含以下几个值中的一个:

如果在compositionstart 事件发生时访问,包含正在编辑的文本(例如,已经选中的需要马上替换的文本);

如果在compositionupdate 事件发生时访问,包含正插入的新字符;

如果在compositionend 事件发生时访问,包含此次输入会话中插入的所有字符。

IE9+是到2011 年唯一支持复合事件的浏览器。由于缺少支持,对于需要开发跨浏览器应用的开发人员,它的用处不大。要确定浏览器是否支持复合事件,可以使用以下代码:var isSupported = document.implementation.hasFeature(“CompositionEvent”, “3.0”);

这一部分我没办法测试,只好当一个搬运工了~

变动事件

DOM2 级的变动(mutation)事件能在DOM 中的某一部分发生变化时给出提示。变动事件是为XML或HTML DOM设计的,并不特定于某种语言。DOM2 级定义了如下变动事件:

DOMSubtreeModified:在DOM结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发这里的任何事件是指任何变动事件)。

DOMNodeInserted:在一个节点作为子节点插入到另一个节点中时触发。

DOMNodeRemoved:在节点从父节点中移除时触发。

DOMNodeInsertedIntoDocument:在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在DOMNodeInserted之后触发。

DOMNodeRemovedFromDocument:在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。这个事件在DOMNodeRemoved 之后触发。

DOMAttrModified:在特性被修改之后触发。

DOMCharacterDataModified:在文本节点的值发生变化时触发。

使用下列代码可以检测出浏览器是否支持变动事件:

var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");


DOM3级事件模块作废了很多变动事件(DOMAttrModified,DOMCharacterDataModified),下面介绍将来仍然会得到支持的事件。

删除节点

在使用removeChild()或replaceChild()从DOM中删除节点时,首先会触发DOMNodeRemoved事件。这个事件的目标(event.target)是被移除的节点。而关联节点(event.relatedNode,注意和relatedTarget的区别)则是该节点的父节点。这个事件触发时,节点还未移除,因此parentNode可以正常使用。这个事件会冒泡,因此可以在DOM的任何层次上面处理这个事件。

除了触发DOMNodeRemoved,还会接着触发DOMNodeRemovedFromDocument事件,如果移除的节点存在子节点,那么子节点上面也会触发DOMNodeRemovedFromDocument事件(这里会做一个实验确定触发顺序)。需要注意的是这个事件不会冒泡,所以只有直接指定给其中的一个节点,事件处理程序才会被调用。这个事件的目标是子节点或者被移除的节点,除此之外event对象中不包含其他信息。

接着触发的是DOMSubtreeModified事件。这个事件的目标是被移除节点的父节点(因为这个节点结构发生了变化),除此之外event对象中不包含其他信息。

<! DOCTYPE html>
<html>
<head>
<title>Node Removal Events Example</title>
</head>
<body>
<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<button onclick="test();">just do it</button>
<script>
document.body.addEventListener("DOMNodeRemoved", function (e) {
console.log("type: " + e.type);
console.log(e.target);//ul
console.log(e.relatedNode);//body
})
var ul = document.getElementById("myList");
function test() {
document.body.removeChild(ul);
}
</script>
</body>
</html>


我为body增加了事件处理程序,说明DOMNodeRemoved事件是会冒泡的,我稍稍修改了一下代码:

var ul = document.getElementById("myList");
var lis = ul.getElementsByTagName("li");
ul.addEventListener("DOMNodeRemovedFromDocument", function (e) {
console.log("type: " + e.type);
console.log(e.target);
console.log(e.relatedNode);
});
for (var i=0,len=lis.length;i<len;i++) {
lis[i].addEventListener("DOMNodeRemovedFromDocument", function (e) {
console.log("type: " + e.type);
console.log(e.target);
console.log(e.relatedNode);
});
}
function test() {
document.body.removeChild(ul);
}


结果是先打印父节点的信息,再打印子节点的信息,且relatedNode为null。我猜测这可能是深度优先的触发顺序,稍微修改一下结构。

<ul id="myList">
<li>
<ul>
<li>Item 00</li>
<li>Item 01</li>
</ul>
</li>
<li>
<ul>
<li>Item 10</li>
<li>Item 11</li>
</ul>
</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>


结果和我想的一样,它的打印结果的确是深度优先的规律。然后我想也许绑定事件处理程序的顺序可能会造成影响,我又改变绑定事件处理程序的顺序做了个测试。

<! DOCTYPE html>
<html>
<head>
<title>Node Removal Events Example</title>
</head>
<body>
<ul id="myList">
<li>
<ul>
<li id="f20">Item 00</li>
<li>Item 01</li>
</ul>
</li>
<li id="f10">
<ul>
<li id="f21">Item 10</li>
<li>Item 11</li>
</ul>
</li>
<li id="f11">Item 2</li>
</ul>
<button onclick="test();">just do it</button>
<script>
var ul = document.getElementById("myList");
var f20 = document.getElementById("f20");
var f10 = document.getElementById("f10");
var f21 = document.getElementById("f21");
var f20 = document.getElementById("f20");
var f11 = document.getElementById("f11");
function justdoit(e) {
console.log("type: " + e.type);
console.log(e.target);
console.log(e.relatedNode);
}
f11.addEventListener("DOMNodeRemovedFromDocument", justdoit);
f21.addEventListener("DOMNodeRemovedFromDocument", justdoit);
f10.addEventListener("DOMNodeRemovedFromDocument", justdoit);
f20.addEventListener("DOMNodeRemovedFromDocument", justdoit);
ul.addEventListener("DOMNodeRemovedFromDocument", justdoit);

function test() {
document.body.removeChild(ul);
}
</script>
</body>
</html>


执行结果依旧是先移除的节点,然后深度优先的规律。我们再为document绑定DOMSubtreeModified看看效果。

document.addEventListener("DOMSubtreeModified", justdoit);


效果就是多打印一个关于body的信息,也证明了这个事件是会冒泡的。

插入节点

在使用appendChild()replaceChild()insertBefore()向DOM中插入节点时,首先会触发DOMNodeInserted 事件。在这个事件触发时,节点已经被插入到了新的父节点中(所以parentNode也可以用了)。其余的特点和DOMNodeRemoved都差不多。

类似的,还会接着在新插入的节点上面触DOMNodeInsertedIntoDocument 事件,这个事件不冒泡,因此必须在插入节点之前为它添加这个事件处理程序。

最后再触发DOMSubtreeModified事件。

这部分的例子我就省略了,和删除节点大同小异。

对事件的感悟

做任何的操作都有可能触发一定的事件,事件触发与否与节点是否绑定事件处理程序是没有必然关系的。事件就是事件,它触发的条件就是一定的操作。而我们为节点绑定事件处理程序,不过是为了捕捉该事件,然后执行一定的行为。换句话说,事件时时刻刻都会触发,而要不要根据事件的触发而执行一定的行为,关键在于我们有没有对节点添加事件处理程序,且该节点能够得知事件的发生(冒泡与否)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  javascript