JavaScript语言基础(十)事件

297 阅读14分钟

事件

JavaScript与HTML的交互是通过事件实现的,事件代表文档或浏览器窗口中某个有意义的时刻。

可以使用仅在事件发生时执行的监听器(也称处理程序)订阅事件。

事件流

描述了页面接收事件的顺序。

事件冒泡

IE事件流被称为事件冒泡。 所有现代浏览器都支持事件冒泡。现代浏览器中的事件会一直冒泡到window对象。

事件捕获

意思是最不具体的节点应该最先收到事件,而最具体的节点应该收到事件。 实际上是为了在事件到达最终目标前拦截事件。

所有浏览器都是从window对象开始捕获事件。

由于旧版本浏览器不支持,因此实际当中几乎不会使用事件捕获,建议使用事件冒泡,特殊情况下可以使用事件捕获。

DOM事件流

DOM2 Events 规范规定事件流分为3个阶段:事件捕获、到达目标和事件冒泡。

事件捕获最先发生,为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。

所有现代浏览器都支持DOM事件流,只有IE8以及更早版本不支持。

事件处理程序(或事件监听器)

即为响应事件而调用的函数。事件处理程序的名字以"on"开头。

HTML事件处理程序

作为事件处理程序执行的代码可以访问全局作用域中的一切。

大多数HTML事件处理程序会封装在try/catch块中。

<input type="button" value = "Click Me" onclick = "try{showMessage();}catch(ex) {}">

DOM0事件处理程序

在JS中指定事件处理程序的传统方式是把一个函数赋值给(DOM元素的)一个事件处理程序属性。

let btn = document.getElementById("myBtn");
btn.click = function(){
	console.log("Clicked");
};

像这样使用DOM0方式为事件处理程序赋值时,所赋函数被视为元素的方法,因此,事件处理程序会在元素的作用域中运行,即this等于元素。

把事件处理程序设置为null,再点击按钮就不会执行任何操作了。

btn.onclick = null; // 移除事件处理程序

DOM2事件处理程序

为事件处理程序的赋值和移除定义了addEventListener()和 removeEventListener()方法, 它们接收3个参数:事件名、事件处理函数和一个布尔值,true标识在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序。

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

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

通过addEventListener()添加的事件处理程序,只能使用removeEventListener()并传入与添加时同样的参数来移除。 意味着使用addEventListener()添加的匿名函数无法移除

btn.removeEventListener("click", function() {	// 没有效果
	console.log(this.id);
}, false);

传给removeEventListener()的事件处理函数必须与传给addEventListener()的是同一个。

let btn = document.getElementById("myBtn");
let handler = function(){
	console.log(this.id);
};
btn.addEventListener("click", handler, false);

btn.removeEventListener("click", handler, false);	// 有效果

大多数情况下,事件处理程序会被添加到事件流的冒泡阶段,主要因为跨浏览器兼容性好。

把事件处理程序注册到捕获阶段,通常用于在事件到达其指定目标之前拦截事件,若不需要拦截,则不要使用事件捕获。

IE事件处理程序

为事件处理程序的赋值和移除定义了attachEvent()与detachEvent()方法,都接收两个同样的参数:事件处理程序的名字和事件处理函数。 因为IE8以及更早版本只支持事件冒泡,所以使用attachEvent()添加的事件处理程序会添加到冒泡阶段

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
	console.log("Clicked");
});

事件处理程序是在全局作用域中运行的,因此this等于window。与使用DOM0方式的this值不同。

attachEvent()也可以给一个元素添加多个事件处理程序,但是这里的事件触发程序会按照添加的顺序反向触发

使用attachEvent()添加的事件处理程序将使用detachEvent()来移除,只要提供相同的参数。 与使用DOM方法类似,作为事件处理程序添加的匿名函数也无法移除。

跨浏览器事件处理程序

要确保事件处理代码具有最大兼容性,只需让代码在冒泡阶段运行即可。

为此,需要先创建一个addHandler()方法,此方法任务是根据需要分别使用DOM0方式、DOM2方式或IE方式来添加事件处理程序。 这个方法会在EventUtil对象(例子中使用的对象)上添加一个方法,以实现跨浏览器事件处理。接收3个参数:目标元素、事件名和事件处理函数。

removeHandler()同样接收3个参数,用于移除之前添加的事件处理程序,不管是通过哪种方式添加的,默认为DOM0方式。

图片.png

事件对象

在DOM中发生事件时,所有相关信息都会被收集并存储在一个名为event的对象中。这个对象包含了一些基本信息,比如 导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其它数据。

DOM事件对象

在DOM合规的浏览器中,event对象是传给事件处理程序的唯一参数。不管以哪种方式(DOM0或DOM2)指定事件处理程序,都会传入这个event对象。

let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
	console.log(event.type);	// "click"
};
btn.addEventListener("click", (event) => {
	console.log(event.type);	// "click"
}, false);

在通过HTML属性指定的事件处理程序中,同样可以使用变量event引用事件对象。

<input type = "button" value = "Click Me" onclick = "console.log(event.type)">

不同的事件生成的事件对象也会包含不同的属性和方法。不过,所有事件对象都会包含以下列出的这个公共属性和方法:

图片.png

图片.png

在事件处理程序内部,this对象始终等于currentTarget的值,而target只包含事件的实际目标。 如果事件处理程序直接添加在了意图的目标,则this、currentTarget和target的值是一样的。

type属性在一个处理程序处理多个事件时很有用,下面的处理程序就使用了event.type:

let btn = document.getElementById("myBtn");
let handler = function(event){
	switch(event.type){
		case "click":
			console.log("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事件处理程序中取消。 任何可以通过preventDefault()取消默认行为的事件,其事件对象的cancelable属性都会设置为true。

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

如,直接添加到按钮的事件处理程序中调用此方法,可以阻止document.body上注册事件处理程序的执行:

let btn = document.getElementById("myBtn");
btn.onclick = function(event){
	console.log("Clicked");
	event.stopPropagation();
};
document.body.onclick = function(event){
	console.log("Body clicked");
};

若不调用stopPropagation(),则点击按钮会打印两条消息。由于click事件不会传播到document.body,因此onclick事件处理程序永远不会执行。

eventPhase属性用于确定事件流当前所处的阶段,若事件处理程序在捕获阶段被调用,则其值为1;若是在目标上被调用,则其值为2;若是在冒泡阶段被调用,则其值为3。 虽然“到达目标”是在冒泡阶段发生的,但其eventPhase仍然是2。

let btn = document.getElementById("myBtn");
btn.onclick = function(event){
	console.log(event.eventPhase);	// 2
};
document.body.addEventListener("click", (event) = >{
	console.log(event.eventPhase);	// 1
}, true);
document.body.onclick = (event) => {
	console.log(event.eventPhase);	// 3
};

首先触发在捕获阶段的document.body上的事件处理程序,接着触发按钮本身的事件处理程序(尽管注册在冒泡阶段),最后触发注册在冒泡阶段的document.body上的事件处理程序。 event对象只在事件处理程序执行期间存在,一旦执行完毕,就会被销毁。

IE事件对象

与DOM事件对象不同,IE事件对象可以基于事件处理程序被指定的方式以不同方式来访问。

如果事件处理程序是使用DOM0方式指定的,则event对象只是window对象的一个属性。

btn.onclick = function(){
	let event = window.event;
	console.log(event.type);	// "click"
};

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

btn.attachEvent("onclick", function(event) { console.log(event.type); // "click" });

与DOM事件对象一样,基于触发的事件类型不同,event对象中包含的属性和方法也不一样。所有IE事件对象都包含以下的公共属性和方法:

图片.png

图片.png 使用事件对象的srcElement属性代替this,不同事件对象上的srcElement属性中保存的都是事件目标:

btn.onclick = function(){
	console.log(window.event.srcElement === this );	// true
};
btn.attachEvent("onclick", function(event) { 
	console.log(event.srcElement === this );	// false,由于事件处理程序运行在全局作用域下,因此不等
});

returnValue属性等价于DOM的preventDefault()方法,都用于取消给定时间的默认行为。只是这里要把returnValue设置为false才是阻止默认动作

cancelBubble属性与DOM的stopPropagation()方法用途一样,都可以阻止事件冒泡。 由于IE8以及更早版本不支持捕获阶段,所以只会取消冒泡。而stopPropagation()既取消捕获也取消冒泡。

let btn = document.getElementById("myBtn");
btn.onclick = function(event){
	console.log("Clicked");
	window.event.cancelBubble = true;
};
document.body.onclick = function(event){
	console.log("Body clicked");
};

可以阻止事件冒泡到document.body,也就阻止了调用注册在它上面的事件处理程序,因此点击按钮只会输出一条信息,即“Clicked”。

跨浏览器事件对象

DOM事件对象中包含IE事件对象的所有信息和能力,只是形式不同。这些共性可以让两种事件模型之间的映射成为可能。 前面提及的EventUtil对象可以添加一些方法:

var EventUtil = {
	addHandler : function(element, type, handler){
		// 之前的代码		
	},

	getEvent : function(event){		// 返回对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){	// 停止事件流的DOM方法
		if(event.stopPropagation){
			event.stopPropagation();
		}else {
			event.cancelBubble= true;
		}
	}
};

事件类型

用户界面事件

用户界面事件或UI事件不一定跟用户操作有关。

UI事件主要有以下几种:

图片.png

1. load事件

可能是JavaScript中最常用的事件。在window对象上,load事件会在整个页面(包括所有外部资源如图片、JavaScript文件和CSS文件)加载完成后触发。

可通过两种方式指定load事件处理程序:

  1. JavaScrip方式【开发时尽量使用此方式
window.addEventListener("load", (event) => {
	console.log("Loaded!!");
});
  1. 向<body>元素添加onload属性
<!DOCTYPE html>
<html>
<head>
	<title>Load Event Example</title>
</head>
<body	onload = "console.log('Loaded!')">

</body>
</html>

图片也会触发load事件,包括DOM和非DOM中的图片。 可以在HTML中直接给/元素的onload属性指定事件处理程序:

<img src = "cln.gif" onload = "console.log('Loaded!')">

同样,也可以使用JavaScript为图片指定事件处理程序。 在通过JS创建新<img>元素时,也可以给这个元素指定一个在加载完成后执行的事件处理程序,关键是在赋值src属性前指定事件处理程序

window.addEventListener("load", () => {
	let image = document.createElement("img");
	image.addEventListener("load", (event) =>{
		console.log(event.target.src);
	});	
	document.body.appendChild(image);
	image.src = "cln.gif";
});

下载图片并不一定要把<img> 元素添加到文档,只要把它设置了src属性就会立即下载

同样的技术也适用于DOM0的Image对象。在DOM出现之前,客户端都使用Image对象预先加载照片。 可以像使用前面(使用createElement()方法创建)的/元素一样使用Image对象,只是不能把后面添加到DOM树。

window.addEventListener("load", () => {
	let image = new Image();
	image.addEventListener("load", (event) =>{
		console.log("Image loaded!!");
	});	
	image.src = "cln.gif";
});

<script>元素会在JS文件加载完成后触发load事件,从而可以动态检测。 与图片不同,要下载JS文件必须同时指定src属性并把<script>元素添加到文档中

window.addEventListener("load", () => {
	let script = document.createElement("script");
	script.addEventListener("load", (event) =>{
		console.log("Loaded");
	});	
	script.src = "example.js";
	document.body.appendChild(script);
});

IE8及更早版本不支持<script>元素触发load事件。

IE和Opera支持<link> 元素触发load事件,因而支持动态检测样式表是否加载完成。

window.addEventListener("load", () => {
	let link = document.createElement("link");
	link.type = "text/css";
	link.rel = "stylesheet";
	link.addEventListener("load", (event) =>{
		console.log("css loaded");
	});	
	link.href = "example.css";
	document.getElementByTagName("head")[0].appendChild(link);
});

与<script>节点一样,在指定href属性并把<link>节点添加到文档之前不会下载样式表。

2. unload事件

会在文档卸载完成后触发。unload事件一般是在从一个页面导航到另一个页面时触发,最常用于清理引用,以避免内存泄漏。

与load事件类似,unload事件处理程序也有两种指定方式:

  1. JavaScrip方式
window.addEventListener("unload", (event) => {
	console.log("Unloaded!!");
});

这种事件生成的event对象在DOM合规的浏览器中只有target属性(值为document)。IE8及更早版本在这个事件上不提供srcElement属性。

  1. 给<body>元素添加onunload属性
<!DOCTYPE html>
<html>
<head>
	<title>Unload Event Example</title>
</head>
<body	onunload = "console.log('Unloaded!')">

</body>
</html>

3. resize事件

当浏览器窗口被缩放到新高度或宽度时,会触发resize事件。这个事件在window上触发。 因此可以通过JavaScript在window上,或者为<body>元素添加onresize属性来指定事件处理程序。 优先使用JavaScript方式:

window.addEventListener("resize", (event) => {
	console.log("Resized!!");
});

浏览器窗口在最大化和最小化时也会触发resize事件。

焦点事件

在页面元素获得或失去焦点时触发。 焦点事件有以下6种:

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

焦点事件中的两个主要事件是focus和blur,在JS早期就得到了浏览器支持。

鼠标和滚轮事件

DOM3 Events定义的9种鼠标事件:

  • click:在用户单击鼠标主键(通常是左键)或按键盘回车时触发。
  • dbclick:在用户双击鼠标主键(通常是左键)时触发。
  • mousedown:在用户按下任意鼠标键时触发。
  • mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。
  • mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发。这个事件不冒泡,也不会在光标经过后代元素时触发。
  • mousemove:在鼠标光标在元素上移动时反复触发。
  • mouseout:在用户把鼠标光标从一个元素移到另一个元素上时触发。移到的元素可以是原始元素的外部元素,也可以是原始元素的子元素。
  • mouseover:在用户把鼠标光标从元素外部移到元素内部时触发。
  • mouseup:在用户释放鼠标键时触发。

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

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

  1. 客户端坐标 鼠标事件都是在浏览器视口中的某个位置上发生的。这些信息被保存在event对象的clientX和clientY属性中,这两个属性表示事件发生时鼠标光标在视口中的坐标,所有浏览器都支持。
div.addEventListener("click", (event) => {
	console.log('Client coordinates : ${event.clientX}, ${event.clientY}');
});

客户端坐标不考虑页面滚动,因此这两个值并不代表鼠标在页面上的位置。

  1. 页面坐标 客户端坐标是事件发生时鼠标光标在客户端视口中的坐标,而页面坐标是事件发生时鼠标光标在页面上的坐标。 通过event对象的pageX和pageY可以获取,这两个属性表示鼠标光标在页面上的位置,因此反映的是光标到页面,而非视口左边与上边的距离。
div.addEventListener("click", (event) => {
	console.log('Page coordinates : ${event.pageX}, ${event.pageY}');
});

在页面没有滚动时,pageX和pageY与clientX和clientY的值相同。

  1. 屏幕坐标 可以通过event对象的screenX和screenY属性获取鼠标光标在屏幕上的坐标。
div.addEventListener("click", (event) => {
	console.log('Screen coordinates : ${event.screenX}, ${event.screenY}');
});
  1. 修饰键 键盘上的修饰键Shift、Ctrl、Alt和Meta,经常用于修改鼠标事件的行为。

DOM规定了4个属性来表示这几个修饰键的状态:shiftKey、ctrlKey、altKey和metaKey,会在各自对应的修饰键被按下时包含布尔值true,没有被按下时包含false。

现代浏览器支持所有四个修饰键。IE8及更早版本不支持metaKey属性。

  1. 相关元素 对mouseover事件来说,事件的主要目标是获得光标的元素,相关元素是失去光标的元素; 对mouseout事件来说,事件的主要目标是失去光标的元素,相关元素是获得光标的元素;

DOM通过event对象的relatedTarget属性提供相关元素的信息,这个属性只有在mouseover和mouseout事件发生时才包含值,其它所有事件的这个属性的值都是null。

  1. 鼠标按键 对mousedown和mouseup事件来说,event对象上会有一个button属性,表示按下或释放的时哪个按键。 DOM为这个button属性定义了3个值:
  • 0表示鼠标主键,
  • 1表示鼠标中键(通常也是滚轮键),
  • 2表示鼠标副键。 按照惯例,鼠标主键通常时左边的按键,副键通常是右边的按键。
  1. 额外事件信息 DOM2 Event规范在event对象上提供了detail属性,以给出关于事件的更多信息。

对鼠标事件来说,detail包含一个数值,表示在给定位置上发生了多少次单击。 detail的值从1开始,每次单击会加1。 如果鼠标在mousedown和mouseup之间移动了,则detail会重置为0。

  1. mousewheel事件 会在用户使用鼠标滚轮时触发,包括在垂直方向上任意滚动。 这个事件会在任何元素上触发,并冒泡到(在IE8中)document和(在所有现代浏览器中)window。

此事件的event对象包含鼠标事件的所有标准信息,此外还有一个名为wheelDelta的新属性, 当鼠标滚轮向前滚动时,wheelDelta每次都是+120;而鼠标滚轮向后滚动时,wheelDelta每次都是-120。

可以为页面上的任何元素或文档添加onmousewheel事件处理程序,以处理所有鼠标滚轮交互。

document.addEventListener("onmousewheel", (event) =>{
	console.log(event.wheelDelta);
});

多数情况下只需知道滚轮滚动的方向,可以通过wheelDelta值的符号知道。

键盘与输入事件

键盘事件包含3个事件:

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

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

当用户按下某个字符键时,首先触发keydown事件,然后触发keypress事件,最后触发keyup事件。前两个事件会在文本框出现变化之前触发,而keyup会在变化之后触发。 键盘事件支持与鼠标事件相同的修饰键。

  1. 键码 对于keydown和keyup事件,event对象的keyCode属性中会保存一个键码,对应键盘上特定的一个键。 对于字母和数字键,keyCode的值与小写字母和数字的ASCII编码一致。 如数字7键的keyCode为55,而字母A键的keyCode为65,而且跟是否按了Shift键无关。 键盘上所有非字符键的键码:

图片.png

图片.png 2. 字符编码

对插入或移除字符的键,所有浏览器都会触发keypress事件,其他键取决于浏览器。

浏览器在event对象上支持charCode属性,只有发生keypress事件时这个属性才会被设置值。包含的是按键字符对应的ASCII编码。通常charCode属性的值为0,在keypress事件发生时则是对应按键键码。

  1. DOM3的变化 DOM3 Events规范并未规定charCode属性,而是定义了key和char两个新属性。

key属性用于替代keyCode,且包含字符串。在按下字符键时,key的值等于文本字符; 在按下非字符键时,key的值时键名。

char属性在按下字符键时与key类似,在按下非字符键时为null。

IE只支持key属性。Safari和Chrome支持keyIdentifier属性,在按下非字符键时返回与key一样的值,对于字符键,keyIdentifier返回以“U+0000”形式表示Unicode值的字符串形式的字符编码。

由于缺乏跨浏览器支持,不建议使用key、keyIdentifier和char

DOM3 Events支持一个名为location属性,该属性是一个数值,表示在哪里按的键。 可能的值:0—默认键,1—左边,2—右边,3—数字键盘,4—移动设备,5—游戏手柄。 与key属性类似,location属性没得到广泛支持,不建议在跨浏览器开发时使用。

最后一个变化时给event对象增加了getMpdifierState()方法,接收一个参数,一个等于Shift、Control、Alt、AltGraph或Meta的字符串,表示要检测的修饰键。 若给定的修饰键处于激活状态(即键被按住),则方法返回true、否则返回false。

  1. textInput事件 DOM3 Events规范增加了一个名为textInput的事件,其在字符被输入到可编辑区域时触发。

作为对keypress的替代,此事件的行为有些不一样。

区别一:keypress会在任何可以获得焦点的元素上触发,而textInput只在可编辑区域上触发。

区别二:textInput只在有新字符被插入时才会触发,而keypress对任何可能影响文本的键都会触发(包括退格键)。

由于此事件主要关注字符,所以在event对象上提供了一个data属性,包含要插入的字符(不是字符编码)。 data的值始终是要被插入的字符,因此如果在按S键时没按Shift键,data的值为“s”;但在按S键同时按Shift键,data的值则是“S”。

event对象上还有一个名为inputMethod属性,该属性表示向控件中输入文本的手段,可能的值如下: 0,表示浏览器无法确定什么输入手段; 1,表示键盘; 2,表示粘贴; 3,表示拖放操作; 4,表示IME; 5,表示表单选项; 6,表示手写; 7,表示语音; 8,表示组合方式; 9,表示脚本。

合成事件【DOM3 Event新增】

用于处理通常使用IME(即输入法编辑器)输入时的复杂输入序列。 IME通常需要同时按下多个键才能输入一个字符,合成事件用于检测和控制这种输入。 合成事件有以下3种:

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

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

  • 在compositionstart事件中,包含正在编辑的文本;
  • 在compositionupdate事件中,包含要插入的新字符;
  • 在compositionend事件中,包含本次合成过程中输入的全部内容。

HTML5事件

1.contextmenu事件

用于表示何时该显示上下文菜单。 实现上下文菜单功能的JS代码:

图片.png

2.beforeunload事件

此事件会在window上触发,用意是给开发者提供阻止页面被卸载的机会。 会在页面即将从浏览器中卸载时触发,若页面需要继续使用,则可以不卸载。 相反,这个事件会向用户显示一个确认框,询问用户确认是希望关闭页面,还是继续留在页面上。

图片.png

内存与性能

在JS中,页面中事件处理程序的数量与页面整体性能直接相关。

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

事件委托

是"过多事件处理程序"的解决方案。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。

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

<body>
    <ul id="myLinks">
        <li id="goSomewhere">Go Somewhere</li>
        <li id="doSomething">Do Something</li>
        <li id="sayHi">sayHi</li>
    </ul>

    <script type="text/javascript">
        let list = document.getElementById("myLinks");

        list.addEventListener("click", (event) =>{
            let target = event.target;

            switch(target.id){
                case "doSomething" :
                    document.title = "I changed";
                    break;
                case "goSomewhere" :
                    location.href = "http://www.baidu.com";
                    break;
                case "sayHi" :
                    console.log("hi");
                    break;
            }
        });
    </script>
</body>

这里给<ul>元素添加了一个onclick事件处理程序,因为所有列表项都是这个元素的后代,所以它们的事件都会向上冒泡,最终都会由这个函数来处理。

所有使用按钮的事件(大多数鼠标实际和键盘事件)都适用于这个解决方案。

相对于之前的技术,事件委托具有以下优点:

  1. document对象随时可用,任何时候都可以给它添加事件处理程序。意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
  2. 只指定一个事件处理程序,既可以节省DOM引用,也可节省时间。
  3. 减少整个页面所需的内存,提升整体性能。

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

删除事件处理程序

除了通过事件委托,来限制浏览器的代码和负责页面交互的JS代码之间的连接之外,还应该及时删除不用的事件处理程序。 很多Web应用性能不佳都是由于无用的事件处理程序长驻内存导致的。

导致这种问题的主要原因:

  1. 删除带有事件处理程序的元素 如通过真正的DOM方法removeChild()和replaceChild()删除节点,最常见的还是使用innerHTML整体替换页面的某一部分。 此时,被innerHTML删除元素上,如果还有事件处理程序,就不会被垃圾收集程序正常清理。

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

在事件处理程序中删除按钮,会阻止事件冒泡。只有事件目标仍然存在于文档中时,事件才会冒泡。

  1. 页面卸载 如果在页面卸载后,事件处理程序没有被清理,则它们仍然会残留在内存中。

一般来说,最好在onunload事件处理程序中趁页面尚未卸载,先删除所有事件处理程序。

模拟事件

DOM事件模拟

任何时候,都可以使用document.createEvent()方法创建一个event对象,接收一个参数,是一个表示要创建事件类型的字符串。 可用的字符串值有:

  • “UIEvents”(DOM3中是UIEvent)—通用用户界面事件、
  • “MouseEvents”(DOM3中是MouseEvent)—通用鼠标事件和
  • “HTMLEvents”(DOM3中没有)—通用HTML事件

创建event对象之后,需要使用事件相关的信息来初始化。

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

  1. 模拟鼠标事件 首先创建鼠标event对象,可以调用createEvent()方法并传入“MouseEvents”参数。这样就返回event对象,此对象有一个initMouseEvent()方法,用于为新对象指定鼠标的特定信息,接收15个参数,分别对应鼠标事件会暴露的属性。 参数如下:

图片.png

图片.png 前四个参数是正确模拟事件的唯一重要的几个参数,是浏览器要用的,其他参数是事件处理程序要用的。 使用默认值模拟单击事件:

let btn = document.getElementById("myBtn");
let event = document.createEvent("MouseEvents");	// 创建event对象
event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);	// 初始化event对象
btn.dispatchEvent(event);	// 触发事件
  1. 模拟键盘事件 在DOM3中创建键盘事件的方式是给createEvent()方法传入参数“KeyboardEvent”,返回的event对象有一个initKeyboardEvent()方法,接收以下参数:

图片.png

图片.png 在使用document.createEvent(“KeyboardEvent”)之前,最好检测一下浏览器对DOM3键盘事件的支持情况,其它浏览器会返回非标准的KeyboardEvent对象。

Firefox允许给createEvent()方法传入参数“KeyEvent”来创建键盘事件,此时返回event对象包含的方法initKeyEvent(),接收以下参数:

图片.png 键盘事件也可以通过调用dispatchEvent()并传入event对象来触发。

  1. 自定义DOM事件 DOM3增加了自定义事件的类型。要创建自定义事件,需要调用createEvent(“CustomEvent”),返回的对象包含initCustomEvent()方法,接收以下参数:

图片.png

IE事件模拟

首先,要使用document对象的createEventObject()方法来参加event对象,此方法不接受参数。 返回的event对象,可以手工给此对象指定希望对象具备的所有属性。 最后在事件目标上调用fireEvent()方法,接收两个参数:事件处理程序的名字和event对象。 调用fireEvent()时,srcElement和type属性会自动指派到event对象,意味着IE支持的所有事件都可通过相同的方式来模拟。

在一个按钮上模拟了click事件:

var btn = document.getElementById("myBtn");

var event = document.createEventObject();	// 创建event对象

event.screenX = 100;	// 初始化event对象
event.screenY = 0;
event.clientX = 0;
event.clientY = 0;
event.ctrlKey = false;
event.altKey = false;
event.shiftKey = false;
event.button = 0;

btn.fireEvent("onclick", event);		// 触发事件