JavaScript学习笔记(15):事件

事件流

事件流描述了页面接收事件的顺序。分为冒泡流捕获流,而早期的IE和Netscape开发团队提出了几乎完全相反的事件流方案,IE支持了事件冒泡流,而Netscape支持了事件捕获流。

事件冒泡

事件冒泡是指事件被定义为从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)。

如点击一个div,事件先从div处触发,之后向上冒泡到它的父容器,最终冒泡到document。这就意味着,如果没在div处处理事件,事件将依次向上传播,直到某一个父级元素将它处理,直到document都没有处理该事件的话,该事件将不再会被处理。

浏览器在实现事件冒泡时可能会有差异,如IE5.5及早期版本会跳过<html>元素,从<body>直接到document,而现代浏览器中的事件会一直冒泡到window对象。

事件捕获

在事件捕获中,点击div的事件首先由document元素捕获,然后沿DOM树依次向下传播,直至到达实际的目标元素div。这是和事件冒泡完全相反的过程。

事件捕获也得到了所有现代浏览器的支持。

DOM事件流

DOM2 Events规范将冒泡和捕获综合起来,规定事件流分为3个阶段:事件捕获到达目标事件冒泡

事件捕获最先发生,为提前拦截事件提供了可能,然后实际的目标元素接收到事件,最后冒泡。事件最迟要在冒泡阶段响应事件,否则该事件将不会被响应。

DOM规范规定的和浏览器实际实现的事件流有些许不同。

首先是事件捕获的起点和冒泡的终点。DOM规范规定从document开始捕获事件,也最终冒泡到document,但所有现代浏览器都是从window对象开始捕获事件,最终冒泡到window对象

其次是事件到达目标的阶段。事件到达目标的阶段可以认为是捕获阶段的结尾,同时又是冒泡阶段的开始。在DOM标准中,只有一个事件到达目标的阶段,而浏览器的实现则将它们分为了两个。也就是说,在事件目标上将有两个机会来处理事件

事件处理程序

为响应事件而调用的函数被称为事件处理程序(或事件监听器)。

事件处理程序的名字以"on"开头,因此click事件的处理程序叫作onclick,而load事件的处理程序叫作onload。

有多种方式可以指定事件处理程序。

HTML事件处理程序

即直接绑定在HTML元素上的事件处理程序,将JS代码直接写在对应的HTML标签中,如:

1
<input type="button" value="Click Me" onclick="console.log('Clicked')" />

以这种方式指定的事件处理程序有一些特殊的地方:

  • 会创建一个函数来封装所有操作,并且这个函数有一个特殊的局部变量event,其中保存的就是event对象,因此在内联在HTML中的事件处理程序中,可以直接访问event这个名称;
  • 事件处理程序中的this相当于绑定事件的元素;
  • 这个包装函数中还可以直接访问document和元素自身的成员,如果这个元素是一个表单元素,那么它还可以直接访问到this.form上的成员。

在HTML中指定事件处理程序有一些问题。

  • 时机问题:有可能HTML元素已经显示在页面上,用户都与其交互了,而事件处理程序的代码可能还无法执行。比如调用了别处的函数时,函数所在的js代码也许还未被加载。这时往往会将处理程序封装在try-catch块中。
  • 对事件处理程序作用域链的扩展在不同浏览器中可能导致不同的结果,上述临时的封装函数中可直接访问的内容可能由于浏览器的实现而不同。
  • HTML与JavaScript强耦合:这违背了良好的设计原则。

DOM0事件处理程序

在JavaScript中把一个函数赋值给DOM元素的一个事件处理程序属性,即可使它成为元素的事件处理程序。如将函数赋值给DOM的onclick属性,那么这个函数就会响应元素的点击事件。如:

1
2
3
4
let btn = document.getElementById("myBtn");
btn.onclick = function () {
console.log("Clicked");
};

在进行绑定之前,元素同样是无法响应事件的。

像这样使用DOM0方式为事件处理程序赋值时,函数被视为元素的方法,因此,函数内this为元素的DOM。

注意以这种方式添加事件处理程序是注册在事件流的冒泡阶段的。

通过将事件处理程序属性的值设置为null,可以移除通过DOM0方式添加的事件处理程序。

DOM2事件处理程序

DOM2标准为DOM对象增加了两个方法以管理事件处理程序:

  • addEventListener():添加事件处理程序。
  • removeEventListener():移除事件处理程序。

它们都接收三个参数:

  • 事件名:一个字符串,如"click",注意没有“on”开头了。
  • 事件处理程序:一个要被注册为事件处理程序的函数。
  • 捕获/冒泡标记:一个布尔值,true表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序。

那么我们可以这样添加事件处理程序:

1
2
3
4
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
console.log(this.id);
}, false);

与DOM0方式类似,这个事件处理程序就算是一个箭头函数,也同样在被附加到的元素的作用域中运行,this的指向也是绑定的元素DOM。

使用DOM2方式的主要优势是可以为同一个事件添加多个事件处理程序,多个事件处理程序以添加顺序来触发。

通过addEventListener()添加的事件处理程序只能使用removeEventListener()并传入与添加时同样的参数来移除。这意味着使用addEventListener()添加的匿名函数无法移除,想要移除必需保留一份函数的引用以便后续移除。有以下例子:

1
2
3
4
5
6
let fun = null;
function setEventListener(dom, event, fn) {
if (fun) dom.removeEventListener(event, fn);
dom.addEventListener(event, fn);
fun = fn;
}

IE事件处理程序

DOM2事件处理程序在IE浏览器中是有兼容性问题的。IE浏览器无法使用DOM2提供的事件绑定和移除函数的,它提供了自己的事件处理机制。

IE实现了与DOM类似的方法,即attachEvent()detachEvent()。这两个方法都接收两个参数:事件处理程序的名字和事件处理函数。

它们与DOM2的addEventListener()removeEventListener()的区别有两个:

  • 没有第三个参数。因为IE8及更早版本只支持事件冒泡,所以使用attachEvent()添加的事件处理程序会默认添加到冒泡阶段
  • 第一个参数事件名称是带有“on”前缀的,即第一个参数是"onclick",而不是DOM2的addEventListener()方法的"click"

使用attachEvent()方法也可以给一个元素添加多个事件处理程序。但与DOM方法不同,这里的事件处理程序会以添加它们的顺序反向触发

跨浏览器事件处理程序

依赖能力检测可以实现一个通用的跨浏览器事件处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const EventUtil = {
addHandler(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(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;
}
}
};

首先检测传入元素上是否存在DOM2方式,如果有DOM2方式,就使用该方式,传入事件类型和事件处理函数,以及表示冒泡阶段的第三个参数false。

否则,如果存在IE方式,则使用该方式,这时候必须在事件类型前加上"on",才能保证在IE8及更早版本中有效。

当两种方式都不存在时,最后使用DOM0方式。

这里的addHandler()removeHandler()方法并没有解决所有跨浏览器一致性问题,比如IE的作用域问题、多个事件处理程序执行顺序问题等。不过,这两个方法已经实现了跨浏览器添加和移除事件处理程序。

事件对象

在DOM中发生事件时,所有相关信息都会被收集并存储在一个名为event的对象中。

所有浏览器都支持这个event对象,尽管支持方式不同。

DOM事件对象

在DOM事件机制中,event对象是传给事件处理程序的唯一参数

事件对象包含与特定事件相关的属性和方法,不同的事件生成的事件对象也会包含不同的属性和方法,但所有事件对象都会包含下表列出的这些公共属性和方法:

事件合并

使用target属性可以实现事件委托。在事件处理程序内部,this对象始终等于currentTarget的值,而target只包含事件的实际目标。如果事件处理程序直接添加在了意图的目标,则this、currentTarget和target的值是一样的,否则(如在事件委托中)它们的值可能不一致。

使用type属性可以实现一个元素的多种事件集中在一个事件处理程序中处理。

事件拦截

preventDefault()方法用于阻止特定事件的默认动作,即取消事件的默认行为。

stopPropagation()方法用于立即阻止事件流在DOM结构中传播,取消后续的事件捕获或冒泡。

有以下例子:

1
2
3
4
5
6
7
8
let btn = document.getElementById("myBtn");
btn.onclick = function (event) {
console.log("Clicked");
event.stopPropagation();
};
document.body.onclick = function (event) {
console.log("Body clicked");
};

如果没有调用stopPropagation()方法则console将会输出两次。

eventPhase属性可用于确定事件流当前所处的阶段:

  • 如果事件处理程序在捕获阶段被调用,则eventPhase等于1;
  • 如果事件处理程序在目标上被调用,则eventPhase等于2;
  • 如果事件处理程序在冒泡阶段被调用,则eventPhase等于3。

由于“到达目标”将会在捕获和冒泡阶段分别发生一次,因此这两次“到达目标”对应的eventPhase都等于2。

IE事件对象

IE事件对象可以基于事件处理程序被指定的方式以不同方式来访问。如果事件处理程序是使用DOM0方式指定的,则event对象只是window对象的一个属性,如下所示:

1
2
3
4
5
const btn = document.getElementById("myBtn");
btn.onclick = function () {
let event = window.event;
console.log(event.type); // "click"
};

这里,window.event中保存着event对象,其event.type属性保存着事件类型。

如果事件处理程序是使用attachEvent()指定的,则event对象会作为唯一的参数传给处理函数,如下所示:

1
2
3
4
const btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function (event) {
console.log(event.type); // "click"
});

使用attachEvent()时,event对象仍然是window对象的属性,只是出于方便也将其作为参数传入。

如果是使用HTML属性方式指定的事件处理程序,则event对象同样可以通过变量event访问。

所有IE事件对象都会包含下表所列的公共属性和方法:

在IE的事件对象中,没有target和currentTarget,反而是有一个srcElement属性。那么这时获取事件绑定对象和事件实际目标需要使用this和srcElement属性:this表示绑定的对象(currentTarget),而srcElement属性表示事件触发的实际对象(target)。

returnValue属性等价于DOM的preventDefault()方法,但在这里要把returnValue设置为false才是阻止默认动作。

cancelBubble属性与DOMstopPropagation()方法用途一样,设置为true可以阻止事件冒泡。因为IE8及更早版本不支持捕获阶段,所以只会取消冒泡。stopPropagation()则既取消捕获也取消冒泡。

跨浏览器事件对象

我们可以扩充之前的EventUtil对象,来实现对DOM事件对象和IE事件对象的映射,实现跨浏览器兼容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const EventUtil = {
addHandler(element, type, handler) {
// 代码略
},
getEvent(event) {
returnevent ? event : window.event;
},
getTarget(event) {
returnevent.target || event.srcElement;
},
preventDefault(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler(element, type, handler) {
// 代码略
},
stopPropagation(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};

使用这些方法的前提是,事件处理程序必须接收event对象,并把它传给这个方法。

事件类型

DOM3 Events定义了如下事件类型:

  • 用户界面事件(UIEvent):涉及与BOM交互的通用浏览器事件。
  • 焦点事件(FocusEvent):在元素获得和失去焦点时触发。
  • 鼠标事件(MouseEvent):使用鼠标在页面上执行某些操作时触发。
  • 滚轮事件(WheelEvent):使用鼠标滚轮(或类似设备)时触发。
  • 输入事件(InputEvent):向文档中输入文本时触发。
  • 键盘事件(KeyboardEvent):使用键盘在页面上执行某些操作时触发。
  • 合成事件(CompositionEvent):在使用某种IME(Input Method Editor,输入法编辑器)输入字符时触发。

此外HTML5还定义了另一组HTML5事件。

浏览器也通常在DOM和BOM上实现专有事件,这些专有事件基本上都是根据开发者需求而不是按照规范增加的,因此不同浏览器的实现可能不同,因此不做过多介绍。

用户界面事件

用户界面事件有以下种类:

  • DOMActivate:元素被用户通过鼠标或键盘操作激活时触发(比click或keydown更通用)。这个事件在DOM3 Events中已经废弃。因为浏览器实现之间存在差异,所以不要使用它。
  • load:在window上当页面加载完成后触发,在窗套(<frameset>)上当所有窗格(<frame>)都加载完成后触发,在<img>元素上当图片加载完成后触发,在<object>元素上当相应对象加载完成后触发。
  • unload:在window上当页面完全卸载后触发,在窗套上当所有窗格都卸载完成后触发,在<object>元素上当相应对象卸载完成后触发。
  • abort:在<object>元素上当相应对象加载完成前被用户提前终止下载时触发。
  • error:在window上当JavaScript报错时触发,在<img>元素上当无法加载指定图片时触发,在<object>元素上当无法加载相应对象时触发,在窗套上当一个或多个窗格无法完成加载时触发。
  • select:在文本框(<input><textarea>)上当用户选择了一个或多个字符时触发。
  • resize:在window或窗格上当窗口或窗格被缩放时触发。
  • scroll:当用户滚动包含滚动条的元素时在元素上触发。<body>元素包含已加载页面的滚动条。

焦点事件

焦点事件在页面元素获得或失去焦点时触发。这些事件可以与document.hasFocus()document.activeElement一起为开发者提供用户在页面中导航的信息。焦点事件有以下6种:

  • blur:当元素失去焦点时触发。这个事件不冒泡,所有浏览器都支持。
  • DOMFocusIn:当元素获得焦点时触发。这个事件是focus的冒泡版。Opera是唯一支持这个事件的主流浏览器。DOM3 Events废弃了DOMFocusIn,推荐focusin。
  • DOMFocusOut:当元素失去焦点时触发。这个事件是blur的通用版。Opera是唯一支持这个事件的主流浏览器。DOM3 Events废弃了DOMFocusOut,推荐focusout。
  • focus:当元素获得焦点时触发。这个事件不冒泡,所有浏览器都支持。
  • focusin:当元素获得焦点时触发。这个事件是focus的冒泡版。
  • focusout:当元素失去焦点时触发。这个事件是blur的通用版。

焦点事件中的两个主要事件是focus和blur,这两个事件在JavaScript早期就得到了浏览器支持。它们最大的问题是不冒泡,因此后来又有了冒泡版。具体过程如下:

  • focuscout在失去焦点的元素上触发。
  • focusin在获得焦点的元素上触发。
  • blur在失去焦点的元素上触发。
  • DOMFocusOut在失去焦点的元素上触发。
  • focus在获得焦点的元素上触发。
  • DOMFocusIn在获得焦点的元素上触发。

其中,blur、DOMFocusOut和focusout的事件目标是失去焦点的元素,而focus、DOMFocusIn和focusin的事件目标是获得焦点的元素。

鼠标与滚轮事件

鼠标事件

鼠标事件是Web开发中最常用的一组事件,这是因为鼠标是用户的主要定位设备。DOM3 Events定义了9种鼠标事件:

  • click:在用户单击鼠标主键(通常是左键)或按键盘回车键时触发。这主要是基于无障碍的考虑,让键盘和鼠标都可以触发onclick事件处理程序。
  • dblclick:在用户双击鼠标主键(通常是左键)时触发。这个事件不是在DOM2 Events中定义的,但得到了很好的支持,DOM3 Events将其进行了标准化。
  • mousedown:在用户按下任意鼠标键时触发。这个事件不能通过键盘触发。
  • mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。mouseenter事件不是在DOM2 Events中定义的,而是DOM3 Events中新增的事件。
  • mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。mouseleave事件不是在DOM2 Events中定义的,而是DOM3 Events中新增的事件。
  • mousemove:在鼠标光标在元素上移动时反复触发。这个事件不能通过键盘触发。
  • mouseout:在用户把鼠标光标从一个元素移到另一个元素上时触发。移到的元素可以是原始元素的外部元素,也可以是原始元素的子元素。这个事件不能通过键盘触发。
  • mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不能通过键盘触发。
  • mouseup:在用户释放鼠标键时触发。这个事件不能通过键盘触发。

页面中的所有元素都支持鼠标事件。

除了mouseenter和mouseleave之外的所有鼠标事件都会冒泡,都可以被取消,而这会影响浏览器的默认行为。

鼠标事件往往不是单个触发的,而是按照一定的顺序组合触发,如用户双击时,以下事件将顺序触发:

  • mousedown
  • mouseup
  • click
  • mousedown
  • mouseup
  • click
  • dblclick

那么前面的事件被取消,则后面的事件就不会被触发了。click和dblclick在触发前都依赖其他事件触发,mousedown和mouseup则不会受其他事件影响。

滚轮事件

鼠标事件还有一个名为滚轮事件的子类别。滚轮事件只有一个事件mousewheel,反映的是鼠标滚轮或带滚轮的类似设备上滚轮的交互。

键盘与输入事件

键盘事件

键盘事件是用户操作键盘时触发的。DOM2 Events最初定义了键盘事件,但该规范在最终发布前删除了相应内容。因此,键盘事件很大程度上是基于原始的DOM0实现的。

DOM3 Events为键盘事件提供了一个首先在IE9中完全实现的规范。其他浏览器也开始实现该规范,但仍然存在很多遗留的实现。

键盘事件包含3个事件:

  • keydown:用户按下键盘上某个键时触发,而且持续按住会重复触发。
  • keypress:用户按下键盘上某个键并产生字符时触发,而且持续按住会重复触发。Esc键也会触发这个事件。DOM3 Events废弃了keypress事件,而推荐textInput事件。
  • keyup:用户释放键盘上某个键时触发。

当用户按下键盘上的某个字符键时,首先会触发keydown事件,然后触发keypress事件,最后触发keyup事件。

输入事件

输入事件只有一个,即textInput。这个事件是对keypress事件的扩展,用于在文本显示给用户之前更方便地截获文本输入。textInput会在文本被插入到文本框之前触发。

那么这些事件发生的顺序为:

  • keydown
  • keypress
  • textInput
  • 文本框发生变化
  • keyup

注意:如果一个字符键被按住不放,keydown和keypress就会重复触发,直到这个键被释放;如果按住某个非字符键不放,则会重复触发keydown事件,直到这个键被释放触发keyup事件。

合成事件

合成事件是DOM3 Events中新增的,用于处理通常使用IME输入时的复杂输入序列。

IME可以让用户输入物理键盘上没有的字符,例如使用IME输入日文。

IME通常需要同时按下多个键才能输入一个字符,合成事件用于检测和控制这种输入。

合成事件有以下3种:

  • compositionstart:在IME的文本合成系统打开时触发,表示输入即将开始;
  • compositionupdate:在新字符插入输入字段时触发;
  • compositionend:在IME的文本合成系统关闭时触发,表示恢复正常键盘输入。

合成事件在很多方面与输入事件很类似。在合成事件触发时,事件目标是接收文本的输入字段。唯一增加的事件属性是data,其中包含的值视情况而异:

  • 在compositionstart事件中,包含正在编辑的文本(例如,已经选择了文本但还没替换);
  • 在compositionupdate事件中,包含要插入的新字符;
  • 在compositionend事件中,包含本次合成过程中输入的全部内容。

变化事件

DOM2的变化事件(Mutation Events)是为了在DOM发生变化时提供通知。

但这些事件已经被废弃,不再介绍。变化事件已经被Mutation Observers所取代。关于Mutation Observers,见“DOM基础”章节。

HTML5事件

DOM规范并未涵盖浏览器都支持的所有事件。很多浏览器根据特定的用户需求或使用场景实现了自定义事件。HTML5详尽地列出了浏览器支持的所有事件。这里介绍一些常用的HTML事件。

contextmenu事件

即用户点击右键触发邮件菜单这一事件。可以取消这一事件然后显示自定义的右键菜单。

contextmenu事件会冒泡,因此只要给document指定一个事件处理程序就可以处理页面上的所有同类事件。事件目标是触发操作的元素。

在DOM合规的浏览器中使用event.preventDefault(),在IE8及更早版本中将event.returnValue设置为false,可以取消该事件。

beforeunload事件

beforeunload事件会在window上触发,用意是给开发者提供阻止页面被卸载的机会。

这个事件会在页面即将从浏览器中卸载时触发,如果页面需要继续使用,则可以不被卸载。

这个事件不能取消,否则就意味着可以把用户永久阻拦在一个页面上。这个事件会向用户显示一个确认框,其中的消息表明浏览器即将卸载页面,并请用户确认是希望关闭页面,还是继续留在页面上。

DOMContentLoaded事件

window的load事件会在页面完全加载后触发,因为要等待很多外部资源加载完成,所以会花费较长时间。而DOMContentLoaded事件会在DOM树构建完成后立即触发,而不用等待图片、JavaScript文件、CSS文件或其他资源加载完成。

DOMContentLoaded可以让开发者在外部资源下载的同时就能指定事件处理程序,从而让用户能够更快地与页面交互。

该事件添加在document或window上,实际的事件目标是document,但会冒泡到window。

readystatechange事件

支持readystatechange事件的每个对象都有一个readyState属性,该属性具有一个以下列出的可能的字符串值:

  • "uninitialized":对象存在并尚未初始化。
  • "loading":对象正在加载数据。
  • "loaded":对象已经加载完数据。
  • "interactive":对象可以交互,但尚未加载完成。
  • "complete":对象加载完成。

并非所有对象都会经历所有readystate阶段。这意味着readystatechange事件经常会触发不到4次,而readyState未必会依次呈现上述值。

当对象的readyState改变时就会触发readystatechange事件。

pageshow与pagehide事件

一些浏览器具有往返缓存功能,即已加载过的页面被缓存在内存里,使用浏览器“前进”和“后退”按钮时不会重新触发load事件,因为往返缓存也存储了DOM和JavaScript的状态。

pageshow事件会在页面显示时触发,无论是否来自往返缓存

在新加载的页面上,pageshow会在load事件之后触发;在来自往返缓存的页面上,pageshow会在页面状态完全恢复后触发。注意,虽然这个事件的目标是document,但其事件处理程序必须添加到window上

pageshow的event对象中还包含一个名为persisted的属性,这个属性是一个布尔值,如果页面存储在了往返缓存中就是true,否则就是false。

与pageshow对应的事件是pagehide,这个事件会在页面从浏览器中卸载后,在unload事件之前触发

与pageshow事件一样,pagehide事件同样是在document上触发,但事件处理程序必须被添加到window

对pagehide事件来说,persisted为true表示页面在卸载之后会被保存在往返缓存中。

hashchange事件

HTML5增加了hashchange事件,用于在URL散列值(URL最后#后面的部分)发生变化时通知开发者。

onhashchange事件处理程序必须添加给window,每次URL散列值发生变化时会调用它。event对象有两个新属性:oldURL和newURL。这两个属性分别保存变化前后的URL,而且是包含散列值的完整URL。

如果想确定当前的散列值,最好使用location对象。

设备事件

设备事件可以用于确定用户使用设备的方式,包括智能手机和平板电脑。

orientationchange事件

orientationchange事件是苹果公司在移动Safari浏览器上创造的。

Safari在window上暴露了window.orientation属性,它有以下3种值之一:0表示垂直模式,90表示左转水平模式(主屏幕键在右侧), -90表示右转水平模式(主屏幕键在左)。

每当用户旋转设备改变了模式,就会触发orientationchange事件。但event对象上没有暴露任何有用的信息,这是因为相关信息都可以从window.orientation属性中获取。

deviceorientation事件

deviceorientation是DeviceOrientationEvent规范定义的事件。

如果可以获取设备的加速计信息,而且数据发生了变化,这个事件就会在window上触发。

要注意的是,deviceorientation事件只反映设备在空间中的朝向,而不涉及移动相关的信息。

devicemotion事件

DeviceOrientationEvent规范也定义了devicemotion事件。这个事件用于提示设备实际上在移动,而不仅仅是改变了朝向。

例如,devicemotion事件可以用来确定设备正在掉落或者正拿在一个行走的人手里。

触摸与手势事件

触摸事件

当手指放在屏幕上、在屏幕上滑动或从屏幕移开时,触摸事件即会触发。触摸事件有如下几种:

  • touchstart:手指放到屏幕上时触发(即使有一个手指已经放在了屏幕上)。
  • touchmove:手指在屏幕上滑动时连续触发。在这个事件中调用preventDefault()可以阻止滚动。
  • touchend:手指从屏幕上移开时触发。
  • touchcancel:系统停止跟踪触摸时触发。文档中并未明确什么情况下停止跟踪。

这些事件都会冒泡,也都可以被取消。

手势事件

手势事件会在两个手指触碰屏幕且相对距离或旋转角度变化时触发。手势事件有以下3种:

  • gesturestart:一个手指已经放在屏幕上,再把另一个手指放到屏幕上时触发。
  • gesturechange:任何一个手指在屏幕上的位置发生变化时触发。
  • gestureend:其中一个手指离开屏幕时触发。

只有在两个手指同时接触事件接收者时,这些事件才会触发。

在一个元素上设置事件处理程序,意味着两个手指必须都在元素边界以内才能触发手势事件(这个元素就是事件目标)。

因为这些事件会冒泡,所以也可以把事件处理程序放到文档级别,从而可以处理所有手势事件。使用这种方式时,事件的目标就是两个手指均位于其边界内的元素。

内存与性能

因为事件处理程序在现代Web应用中可以实现交互,所以很多开发者会错误地在页面中大量使用它们,这会导致页面性能的下降,原因主要有:

  • 每个函数都是对象,都占用内存空间,对象越多,性能越差。
  • 为指定事件处理程序所需访问DOM的次数会先期造成整个页面交互的延迟。

事件委托

“过多事件处理程序”的解决方案是使用事件委托。

事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件

例如,click事件冒泡到document,这意味着可以为整个页面指定一个onclick事件处理程序,而不用为每个可点击元素分别指定事件处理程序。

事件委托有以下优点:

  • document对象随时可用,任何时候都可以给它添加事件处理程序(不用等待DOMContentLoaded或load事件)。这意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
  • 节省花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省DOM引用,也可以节省时间。
  • 减少整个页面所需的内存,提升整体性能。

最适合使用事件委托的事件包括:click、mousedown、mouseup、keydown和keypress。

删除事件处理程序

把事件处理程序指定给元素后,在浏览器代码和负责页面交互的JavaScript代码之间就建立了联系。

这种联系建立得越多,页面性能就越差。

除了通过事件委托来限制这种连接之外,还应该及时删除不用的事件处理程序。

很多Web应用性能不佳都是由于无用的事件处理程序长驻内存导致的。

导致这个问题的原因主要是删除了带有事件处理程序的元素:

  • 通过DOM方法removeChild()replaceChild()删除节点;
  • 使用innerHTML整体替换页面的某一部分。

这时候,被删除的元素上如果有事件处理程序,就不会被垃圾收集程序正常清理。

因此如果知道某个元素要会被删除,那么最好在删除它之前手工删除它的事件处理程序。

另一个可能导致内存中残留引用的问题是页面卸载:如果在页面卸载后事件处理程序没有被清理,则它们仍然会残留在内存中。之后,浏览器每次加载和卸载页面(比如通过前进、后退或刷新),内存中残留对象的数量都会增加,这是因为事件处理程序不会被回收。

因此,最好在onunload事件处理程序中趁页面尚未卸载先删除所有事件处理程序。这时候也能体现使用事件委托的优势,因为事件处理程序很少,所以很容易记住要删除哪些。

但是注意在页面中使用onunload事件处理程序意味着页面不会被保存在往返缓存(bfcache)中。如果希望使用往返缓存,那么就只在IE中使用onunload事件好了。

模拟事件

通过JavaScript在任何时候触发任意事件,而这些事件会被当成浏览器创建的事件。

这意味着同样会有事件冒泡,因而也会触发相应的事件处理程序。

这在对Web应用进行测试时非常有用。

DOM事件模拟

都可以使用document.createEvent()方法创建一个event对象。这个方法接收一个参数,此参数是一个表示要创建事件类型的字符串。在DOM2中,所有这些字符串都是英文复数形式,但在DOM3中,又把它们改成了英文单数形式。可用的字符串值是以下值之一。

  • "UIEvents"(DOM3中是"UIEvent"):通用用户界面事件(鼠标事件和键盘事件都继承自这个事件)。
  • "MouseEvents"(DOM3中是"MouseEvent"):通用鼠标事件。
  • "HTMLEvents"(DOM3中没有):通用HTML事件(HTML事件已经分散到了其他事件大类中)。

注意,键盘事件不是在DOM2 Events中规定的,而是后来在DOM3 Events中增加的。

创建event对象之后,需要使用事件相关的信息来初始化,每种类型的event对象都有特定的方法,可以使用相应数据来完成初始化,各种事件用于初始化的方法名字并不相同。

事件模拟的最后一步是触发事件,使用DOM对象的dispatchEvent()方法,这个方法存在于所有支持事件的DOM节点之上。dispatchEvent()方法接收一个参数,即表示要触发事件的event对象。

具体模拟事件的API不再介绍。使用时翻阅文档。

IE事件模拟

在IE8及更早版本中模拟事件的过程与DOM方式类似:创建event对象,指定相应信息,然后使用这个对象触发。

首先,要使用document对象的createEventObject()方法来创建event对象。与DOM不同,这个方法不接收参数,返回一个通用event对象。

然后,可以手工给返回的对象指定希望该对象具备的所有属性,而没有初始化方法。

最后一步是在事件目标上调用fireEvent()方法,这个方法接收两个参数:事件处理程序的名字和event对象。调用fireEvent()时,srcElement和type属性会自动指派到event对象(其他所有属性必须手工指定)。

这意味着IE支持的所有事件都可以通过相同的方式来模拟。




* 你好,我是大森。如果文章内容帮到了你,你可通过下方付款二维码支持作者 *