JavaScript中的事件

469 阅读5分钟

事件流

JavaScript操作css称为脚本化CSS,而JavaScript与HTML的交互是通过事件实现的。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间,而事件流(又叫事件传播)描述的是从页面中接收事件的顺序。

事件流的三个阶段

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

事件捕获

不太具体的节点(window/document)更早的接收事件,往具体的节点进行传播,最具体的节点应该在最后接收到事件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1,maximum=1,user-scalable=no">
    <title>Document</title>
	<style type="text/css">
		#box{
			width: 200px;
			height: 200px;
			background-color: red;
		}
	</style>
</head>
<body>
    <div id="box"></div> 
	<script type="text/javascript">
		var box = document.getElementById("box");
		box.addEventListener('click',function(){
			box.innerHTML += "div\n"
		},true);//false 表示冒泡阶段
		document.body.addEventListener('click',function(){
			box.innerHTML += "body\n"
		},true);
		document.documentElement.addEventListener('click',function(){
			box.innerHTML += "html\n"
		},true);
		document.addEventListener('click',function(){
			box.innerHTML += "document\n"
		},true);
		window.addEventListener('click',function(){
			box.innerHTML += "window\n"
		},true);
		
	</script>
</body>
</html>

事件冒泡

事件开始时由最具体的节点接收,然后逐级向上传播到较为不具体的节点(文档)

注意:ie9以下仅冒泡到document

测试代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1,maximum=1,user-scalable=no">
    <title>Document</title>
	<style type="text/css">
		#box{
			width: 200px;
			height: 200px;
			background-color: red;
		}
	</style>
</head>
<body>
    <div id="box"></div> 
	<script type="text/javascript">
		var box = document.getElementById("box");
		box.onclick = function(){
			box.innerHTML += "div\n"
		}
		document.body.onclick = function(){
			box.innerHTML += "body\n"
		} 
		document.documentElement.onclick = function(){
			box.innerHTML += "html\n"
		}
		document.onclick = function(){
			box.innerHTML += "document\n"
		}
		window.onclick = function(){
			box.innerHTML += "window\n"
		}
	</script>
</body>
</html>

事件处理程序

事件处理程序又叫事件侦听器,实际上就是事件的绑定函数。事件发生时会执行函数中相应代码。

事件处理程序分为四种:

  • HTML事件处理程序
  • DOM 0级事件处理程序
  • DOM 2 级事件处理程序
  • IE 事件处理程序

HTML事件处理程序

直接在元素上绑定事件,不常用

在事件处理程序函数内部,this指向事件的目标元素。

<div id="box" onclick="this.innerHTML+='1'"></div>
这里的this就是当前对象(<div id="box" onclick="this.innerHTML+='1'"></div>)

等价于

<div id="box" onclick="test()"></div> 
<script type="text/javascript">
	var box = document.querySelector("#box");
	function test(){
		console.log(this);//此处的this指向window,函数独立调用,内部this指向window
		box.innerHTML +='1';
	}
</script>

缺点

html+js混合在一起,不易维护。

DOM 0级事件处理程序

将一个函数赋值给一个事件的处理程序的属性, 应用非常多(简单,跨浏览器)

注意

以DOM 0级方式添加的事件处理程序会在事件流的冒泡阶段被处理,不存在捕获阶段

<div id="box"></div> 
<script type="text/javascript">
	var box = document.querySelector("#box");
	box.onclick = function(){
		this.innerHTML +='1';
	}
    //置空,删除事件的处理程序
    box.onclick = null;
</script>

缺点

不能给同一个元素绑定相同的事件处理程序,如果绑定了,前者会被覆盖

DOM 2级事件处理程序

处理程序存在两种方法

  • addEventListener()
  • removeEventListener()

addEventListener()

事件监听,addEventListener("事件名字符串",function(){},布尔值),其中布尔值默认为false,可以不写,false表示处于冒泡阶段,为true表示处于捕获阶段。

<div id="box"></div> 
<script type="text/javascript">
	var box = document.querySelector("#box");
	box.addEventListener('click', function(){
		this.innerHTML +='1';
	},false);
</script>

DOM 2级事件处理程序,能给同一个元素绑定相同的事件处理程序,同时调用。

注意

ie8浏览器不支持DOM 2级事件处理程序

监听函数传参,可以用匿名函数包装一个监听函数。

<div id="box"></div> 
<script type="text/javascript">
	var box = document.querySelector("#box");
	box.addEventListener('click', function(){
		test(111);
	},false);
	function test(x){
		alert(x);
	}
</script>

removeEventListener()

移除事件

<div id="box"></div> 
<script type="text/javascript">
	var box = document.querySelector("#box");
	box.addEventListener('click',handler,false);
	function handler(){
		this.innerHTML += 1;
	}
	box.removeEventListener('click',handler,false);
</script>

IE的事件处理程序

只能在IE浏览器中使用,处理程序存在两种方法。

在ie中此this指向window

  • attachEvent()
  • detachEvent()

attachEvent()

添加事件

<div id="box"></div> 
<script type="text/javascript">
	var box = document.querySelector("#box");
	box.attachEvent('onclick',function(){
		//this.innerHTML += '1';
		//在ie中此this指向window
		box.innerHTML += '1';
	});
</script>

detachEvent()

移除事件

<div id="box"></div> 
<script type="text/javascript">
	var box = document.querySelector("#box");
	box.attachEvent('onclick',handler);
	function handler(){
		box.innerHTML += '1';
	}
	box.detachEvent('onclick',handler);
</script>

事件绑定兼容写法

代码如下

<body>
    <button type="button">haha</button>
	<script type="text/javascript">
		var btn = document.querySelector("[type=button]");
		// btn.addEventListener('click',fn,false);
		// btn.attachEvent('onclick',fn);
		addEvent(btn,'click',function(){
			console.log(this.innerHTML);
		});
		// 全浏览器事件处理程序的兼容性代码
		function addEvent(target,eventType,handler){
			if(target.addEventListener){
				target.addEventListener(eventType,handler,false);
			}
			else{
				target.attachEvent('on'+eventType,function(){
					handler.call(target);
				});
			}
		}
	</script>
	
</body>

事件调用顺序总结

相同点

如果同时出现html事件处理程序和dom0级事件处理程序,后者会覆盖前者

不同点

1.chrome,safari,火狐以及IE11浏览器结果:dom0级 dom2级
2.IE9、10结果为:dom0级 dom2级 IE
3.IE8结果为:dom0级 IE

事件对象

在触发dom上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息

如何获取事件对象

1.event对象是事件程序的第一个参数,ie8不支持

<body>
    <div id="box"></div>
	<script type="text/javascript">
		window.onload = function(){
			var box = document.getElementById("box");
			box.onclick = function(e){
				box.innerHTML = e;
			}
		}
	</script>
</body>

2.直接使用event变量,火狐浏览器不支持

<body>
    <div id="box"></div>
	<script type="text/javascript">
		window.onload = function(){
			var box = document.getElementById("box");
			box.onclick = function(){
				box.innerHTML = event;
			}
		}
	</script>
</body>

3.兼容写法

<body>
    <div id="box"></div>
	<script type="text/javascript">
		window.onload = function(){
			var box = document.getElementById("box");
			box.onclick = function(e){
                e = e ||window.event;
			   box.innerHTML = e;
			}
		}
	</script>
</body>

事件目标

有三个属性

  • currentTarget
  • target
  • srcElement

currentTarget

返回事件当前所在的节点,正在执行的监听函数所绑定的节点

<body>
    <ul id="box">
		<li class="item">1</li>
		<li class="item">2</li>
    </ul>
	<script type="text/javascript">
		var box = document.getElementById("box");
		box.onclick = function(e){
			e = e || event;
			console.log(e.currentTarget);
			var items = document.querySelectorAll("[class]");
			items[0].innerHTML = e.currentTarget;//[object HTMLUListElement]
		}
	</script>
</body>

target

返回的是事件的实际目标对象

this对象跟e.currentTarget属性是一样的,但 不支持ie8

<body>
    <ul id="box">
		<li class="item">1</li>
		<li class="item">2</li>
    </ul>
	<script type="text/javascript">
		var box = document.getElementById("box");
		box.onclick = function(e){
			e = e || event;
			console.log(e.target);
			console.log(e.target===this);
			// this对象跟e.currentTarget属性是一样的
			console.log(e.currentTarget===this);
			var items = document.querySelectorAll("[class]");
			items[0].innerHTML = e.target;//[object HTMLUListElement]
		}
	</script>
</body>

srcElement

与target属性一样,但target不支持ie8,srcElement属性在低版本的火狐上不支持

兼容

var box = document.getElementById("box");
		box.onclick = function(e){
			e = e || event;
			var target = e.target || e.srcElement;
			
		}

事件代理

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1,maximum=1,user-scalable=no">
    <title>Document</title>
	<style type="text/css">
		*{
			padding: 0;
			margin: 0;
		}
		ul{
			list-style: none;
			overflow: hidden;
			margin-top: 80px;
		}
		li{
			width: 100px;
			float: left;
			height: 30px;
			text-align: center;
			line-height: 30px;
			background-color: red;
			margin: 0px 10px;
			color: white;
			}
	</style>
</head>
<body>
    <ul id="box">
		<li>1</li>
		<li>2</li>
		<li>3</li>
		<li>4</li>
		<li>5</li>
    </ul>
	<script type="text/javascript">
		window.onload = function(){
			// 常规方法
			var lis = document.getElementsByTagName("li");
			for (var i = 0;i<lis.length;i++){
				lis[i].onmouseover =  function(){
					this.style.backgroundColor = 'blue';
				}
				lis[i].onmouseout = function(){
					this.style.backgroundColor = 'red'
				}
			}
			
			//事件代理方式的实现,要结合事件目标对象来实现
			var box = document.getElementById('box');
			box.onmouseover = function(e){
				e = e ||event;
				var target = e.target || e.srcElement;
				target.style.backgroundColor = 'blue'
			}
			box.onmouseout = function(e){
				e = e ||event;
				var target = e.target || e.srcElement;
				target.style.backgroundColor = 'red'
			}
		}
	</script>
</body>
</html>

优点

提高性能、减少代码

事件冒泡

事件冒泡是事件流的第三个阶段,通过事件冒泡可以在这个阶段对事件做出事件响应。

关于冒泡,事件对象中包含四个相关的属性和方法

  • bubbles
  • cancelBubble
  • stopPropagation()
  • stopImmediatePropagation()

bubbles

返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性。

注意

发生在文档上的大部分事件都会冒泡,但focus、blur、scroll事件不会冒泡
<body>
	<button>按钮</button>
	<input type="text" name="" id="" value="" />
	<script type="text/javascript">
		var btn = document.getElementsByTagName('button')[0];
		var inP = document.querySelector('[type=text]');
		btn.onclick = function(e){
			e = e || window.event;
			console.log(e.bubbles);//true
		}
		inP.onfocus = function(e){
			e = e || window.event;
			console.log(e.bubbles);//false
		}
	</script>
</body>

stopPropagation()

表示取消事件的进一步捕获或冒泡,无返回,ie8不支持

<body>
	<button>按钮</button>
	<script type="text/javascript">
		var btn = document.getElementsByTagName('button')[0];
		btn.onclick = function(e){
			e = e || window.event;
			// 阻止冒泡
			e.stopPropagation();
			this.innerHTML = '阻止冒泡';
		}
		document.body.onclick = function(e){
			e = e || window.event;
			console.log('body');
		}
	</script>
</body>

缺点

无法阻止同一事件的其他监听函数被调用

<body>
	<button>按钮</button>
	<script type="text/javascript">
		var btn = document.getElementsByTagName('button')[0];
		btn.addEventListener('click',function(e){
			e = e || window.event;
			e.stopPropagation();
			this.style.backgroundColor = 'blue';
			
		},false);
		btn.addEventListener('click',function(e){
			e = e || window.event;
			// e.stopPropagation();
			this.innerHTML = '阻止了';
			
		},false);
		document.body.onclick = function(e){
			e = e || window.event;
			console.log('body');
		}
	</script>
</body>

stopImmediatePropagation()

既可以阻止冒泡,又可以阻止同一事件的其他监听函数被调用

<body>
	<button>按钮</button>
	<script type="text/javascript">
		var btn = document.getElementsByTagName('button')[0];
		btn.addEventListener('click',function(e){
			e = e || window.event;
			e.stopImmediatePropagation();
			this.style.backgroundColor = 'blue';
			
		},false);
		btn.addEventListener('click',function(e){
			e = e || window.event;
			// e.stopPropagation();
			this.innerHTML = '阻止了';
			
		},false);
		document.body.onclick = function(e){
			e = e || window.event;
			console.log('body');
		}
	</script>
</body>

cancelBubble

只能用于阻止冒泡,无法阻止捕获阶段,该值可读写,默认为false,设置为true,即取消冒泡

<body>
	<button>按钮</button>
	<script type="text/javascript">
		var btn = document.getElementsByTagName('button')[0];
		btn.onclick = function(e){
			e = e || window.event;
			e.cancelBubble = true;
			console.log(e.bubbles);
		}
		document.body.onclick = function(e){
			e = e || window.event;
			console.log('body');
		}
	</script>
</body>

兼容

stopPropagation()和stopImmediatePropagation()ie8不支持
e.cancelBubble = true;全浏览器支持,不是标准写法
<body>
	<button>按钮</button>
	<script type="text/javascript">
		var btn = document.getElementsByTagName('button')[0];
		btn.onclick = function(e){
			e = e || window.event;
			if(e.stopPropagation){
				e.stopPropagation();
			}
			else{
				e.cancelBubble = true;
			}
			this.innerHTML = '修改了'
		}
		document.body.onclick = function(e){
			e = e || window.event;
			console.log('body');
		}
	</script>
</body>

事件流阶段(了解)

eventPhase

返回一个整数值,表示事件目前所处的事件流阶段

0表示事件没有发生,1表示捕获阶段,2表示目标阶段,3表示冒泡阶段,ie8浏览器不支持

<body>
	<button type="button">事件流</button>
	<script type="text/javascript">
		var btn = document.getElementsByTagName('button')[0];
		//2 目标阶段	
		btn.onclick = function(e){
			e = e || event;
			console.log(e.eventPhase);
		}
		
		//1 捕获阶段
		document.body.addEventListener('click',function(e){
			e = e || event;
			console.log(e.eventPhase);
		},true)
		//3冒泡阶段
		document.body.addEventListener('click',function(e){
			e = e || event;
			console.log(e.eventPhase);
		},false)
	</script>
</body>

取消默认事件

平时的方法

<a href="javascript:void(0);">百度</a>
<a href="javascript:;">百度</a>

事件对象中的两个方法,阻止默认事件:

  • preventDefault()
  • returnValue
  • return false

preventDefault()

ie8以下不支持

returnValue

火狐、ie8以上不支持

return false

小技巧,兼容所有浏览器

<body>
	<a href="#">百度</a>
	<a href="javascript:;">百度</a>
	<script type="text/javascript">
		var item = document.getElementsByTagName('a')[0];
		item.onclick = function(e){
			e = e||event;
			
			
			/* // preventDefault()
			e.preventDefault();
			this.innerHTML = 'john'; */
			
			
			/* //returnValue
			e.returnValue = false; */
			
			//兼容ie8以上
			if(e.preventDefault){
				e.preventDefault();
			}else{
				//兼容ie8以下
				e.returnValue = false;
			}
			
			/* //小技巧
			return false; */
		}
	</script>
</body>

事件对象属性

鼠标坐标位置

关于坐标位置,事件对象提供了clientX/YpageX/YscreenX/Yx/yoffsetX/YlayerX/Y

clientX/Y & x/y

相对于浏览器(浏览器的有效区域)的x轴和y轴的距离。

<body>
	<div id="box"></div>
	<script type="text/javascript">
		var box = document.getElementsByTagName('div')[0];
		box.onmousemove = function(e){
			e = e || window.event;
			console.log(e);
			
			this.innerHTML = `clientX:${e.clientX};clientY:${e.clientY};x:${e.x};y:${e.y}`
			
		}
	</script>
</body>

screenX/Y

相对于显示器屏幕的x轴和y轴的距离。

pageX/Y

相对于页面x轴和y轴的距离,会随滚动条的变化而变化

offsetX/Y

相对于事件源的x轴和y轴的距离。