一 事件的绑定
要想让 JavaScript 对用户的操作作出响应,首先要对 DOM 元素绑定事件处理函数。所谓事件处理函数,就是处理用户操作的函数,不同的操作对应不同的名称。
在JavaScript中,有三种常用的绑定事件的方法:
//1. 在DOM元素中直接绑定(注册行内事件);
//2. 在JavaScript代码中绑定;
//3. 绑定事件监听函数
1.1 在DOM中直接绑定事件
我们可以在DOM元素上绑定onclick、onmouseover、onmouseout、onmousedown、onmouseup、ondblclick、onkeydown、onkeypress、onkeyup等 DOM事件 。
//先阻止a标签的默认行为,再绑定点击事件
<a href="javascript:;" onclick="hello()">
<script>
function hello(){
alert("hello world!");
}
</script>
1.2 在JavaScript代码中绑定事件
在JavaScript代码中(即 script 标签内)绑定事件可以使JavaScript代码与HTML标签分离,文档结构清晰,便于管理和开发。
//先阻止a标签的默认行为,再绑定点击事件
<a href="javascript:;" id="a-btn">
<script>
document.querySelector('#a-btn').onclick = function(e){
//阻止默认行为
e = e || window.event;
e.preventDefault();
//功能代码 doSomething......
alert("hello world!");
};
</script>
1.3 使用事件监听绑定事件
//可以给同名的事件,绑定多个事件处理程序
//语法:对象.addEventListener(参数1,参数2,参数3);
//参数1:事件名(字符串),不要加on 例如:click 、 mouseover 、mouseout
//参数2:事件处理程序(函数名),当事件触发后哪个函数来处理
//参数3:是一个bool类型,可以不传,默认为fasle(代表冒泡)跟冒泡和捕获有关
//如果是true则表示捕获阶段
//如果有同名事件不会覆盖,而是会依次执行
//IE8及以前的版本不支持
1.3.1 事件监听的好处
// 1. 可以绑定多个事件,常规的事件绑定只执行最后绑定的事件。
// 2. 可以解除相应的绑定
//绑定多个事件
btn.addEventListener('click',function ( ) {
alert('11111');
},false);
btn.addEventListener('click',function ( ) {
alert('2222');
},false);
//解除事件的绑定
<input type="button" value="click me" id="btn5">
<script>
var btn5 = document.getElementById("btn5");
btn5.addEventListener("click",hello1);//执行了
btn5.addEventListener("click",hello2);//不执行
btn5.removeEventListener("click",hello2);
function hello1(){
alert("hello 1");
}
function hello2(){
alert("hello 2");
}
</script>
二 事件冒泡与事件捕获:
起初Netscape制定了JavaScript的一套事件驱动机制(即事件捕获)。随即IE也推出了自己的一套事件驱动机制(即事件冒泡)。最后W3C规范了两种事件机制,分为捕获阶段、目标阶段、冒泡阶段。IE8以前IE一直坚持自己的事件机制(前端人员一直头痛的兼容性问题),IE9以后IE也支持了W3C规范。
这就说明:
当一个DOM事件被触发时,它不仅仅只是单纯地在本身对象上触发一次,而是会经历三个不同的阶段:
// 1. 捕获阶段 先由文档的根节点document往事件触发对象,从外向内捕获事件对象;
// 2. 目标阶段 到达目标事件位置(事发地),触发事件;
// 3. 冒泡阶段 再从目标事件位置往文档的根节点方向回溯,从内向外冒泡事件对象。
2.1 事件冒泡:
如果一个元素的事件被触发,那么它所有的父级元素的同名事件都会被触发.
注意点:只有当父级拥有同名事件的时候才会被触发.
window.onclick = function () {
alert("window被点击了");
}
document.onclick = function () {
alert("文档被点击了");
}
document.documentElement.onclick = function () {
alert("html被点击了");
}
document.body.onclick = function () {
alert("body被点击了");
}
document.getElementById("box").onclick = function () {
alert("我是骚粉的大盒子");
};
上述代码的执行顺序是:
box元素--> body-->HTML-->document-->window
2.2 事件捕获:
从最顶级的父元素一级一级往下找子元素触发同名事件,直到触发事件的元素为止.
注意点1:是去寻找与与父元素注册的同名事件的子元素
注意点2:因为事件捕获,只能通过addEventListener并且参数写true才是事件捕获
注意点3:其他情况都是冒泡(不是通过addEventListener添加、addEventListener参数为false)
window.addEventListener("click", function () {
alert("这是window");
},true)
document.addEventListener("click", function () {
alert("这是document");
},true)
document.documentElement.addEventListener("click", function (e) {
e = e || window.event;
alert("这是html");
// e.stopPropagation();//阻止事件冒泡和事件捕获
},true)
document.body.addEventListener("click", function () {
alert("这是body");
},true)
//参数3:默认是false,代表是支持事件冒泡
box.addEventListener("click", function () {
alert("这是box");
},true)
上述代码的执行顺序是:
window-->document-->HTML-->body-->box元素
我们知道了事件捕获以及事件的冒泡原理,但是有的时候,我们就只想让其子元素单独触发或者让其父元素单独触发,此时就需要用到阻止事件的捕获以及冒泡的方法.
* 1.阻止事件冒泡:让同名事件不要在父元素中冒泡(触发)
* 说人话:点击一个元素只会触发当前元素的事件,不会触发父元素的同名事件
* 语法: 事件对象.stopPropagation() IE8及之前不支持
* 事件对象.cancelBubble = true IE8之前支持
* 注意:如果想要阻止事件冒泡,一定要在触发事件的函数中接收事件对象
*2.阻止事件捕获:
*事件对象.stopPropagation() 除了可以阻止冒泡还可以阻止捕获
*IE8及以前没有捕获!
2.2.1 利用事件的冒泡写一个案例:
需求:移入li标签颜色改变.
思路:如果想给父元素的多个子元素添加事件,我们可以只需要给父元素添加事件即可,然后通过获取事件源(e.target)就可以得知是哪一个子元素触发了这个事件
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
ul {
border: 1px solid red;
width: 500px;
text-align: center;
margin: 100px auto;
}
#ul1 li {
list-style: none;
line-height: 30px;
font-size: 20px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<ul id="ul1">
<li>我是第1个li标签</li>
<li>我是第2个li标签</li>
<li>我是第3个li标签</li>
<li>我是第4个li标签</li>
<li>我是第5个li标签</li>
<li>我是第6个li标签</li>
<li>我是第7个li标签</li>
<li>我是第8个li标签</li>
<li>我是第9个li标签</li>
<li>我是第10个li标签</li>
</ul>
<script>
//需求: 1.给10个li标签移入添加背景高亮
// 2.移出背景颜色恢复
//思路:
// 给ul注册鼠标移入事件,通过获取事件源(e.target)就可以得知是哪一个子元素触发了这个事件
var ul1 = document.getElementById("ul1");
var DefalutColor = null; //全局变量存储默认的背景色
ul1.onmouseover = function(e){
//获取到的是对应的li标签对象
// console.log(e.target);
DefalutColor = e.target.style.backgroundColor; //将li标签默认的背景色赋给defalutColor
e.target.style.backgroundColor = 'yellow';
}
ul1.onmouseout = function(e){
e.target.style.backgroundColor = DefalutColor; //li标签默认的背景色
}
</script>
</body>
</html>
三 事件委托(事件代理)
3.1 什么是事件委托
就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
//非常经典的一个例子
有三个同事预计会在周一收到快递。为签收快递,有两种办法:
一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。
前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。
这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。
这里其实还有2层意思的:
第一,现在委托前台的同事是可以代为签收的,即程序中的现有的dom节点是有事件的;
第二,新员工也是可以被前台MM代为签收的,即程序中新添加的dom节点也是有事件的
3.2 为什么用事件委托
①. 优化性能:当给10000个li注册点击事件的时候,只需要委托个它的父元素,这样js与dom元素的交互就变为一次,减少了浏览器重绘与重排的次数
②. 减少了内存,给10000个li注册点击事件,每一个li都会有一个事件函数保存在内存里,10000个相同的事件函数与一个事件函数的内存,内存节省太多.同时如果10000个li注册点击事件会造成内存溢出.
③. 可以后来新添加的动态元素绑定事件(非常重要)
3.3 事件委托原理
利用的就是前面讲的冒泡原理:比如给li点击事件,事件先开始捕获阶段,从body->ul->li,而li是目标元素,此时处于目标阶段,浏览器就会查看是否有点击事件,发现没有,那么进入第三个阶段冒泡,又从li->ul,发现ul身上有点击事件,那么便触发ul的点击事件.
3.4 实例实现事件委托
每一个按钮拥有点击事件,但是注册的时候却只给父元素div注册点击事件,同时实现点击不同按钮操作功能不同.
<div id="box">
<input type="button" id="add" value="添加" />
<input type="button" id="remove" value="删除" />
<input type="button" id="move" value="移动" />
<input type="button" id="select" value="选择" />
</div>
//js代码
window.onload = function(){
//父元素
var Box = document.getElementById("box");
Box.onclick = function (e) {
var e = e || window.event;
var target = e.target;
if(target.nodeName.toLocaleLowerCase() == 'input'){
switch(target.id){
case 'add' :
alert('添加');
break;
case 'remove' :
alert('删除');
break;
case 'move' :
alert('移动');
break;
case 'select' :
alert('选择');
break;
}
}
}
}
四 jQuery注册事件
我们现在来探讨jQuery里面的关于事件的操作:
jQuery对JavaScript事件进行了封装,增加并扩展了事件处理机制。jQuery不仅提供了更加优雅的事件处理语法,而且极大的增强了事件的处理能力。
4.1 jQuery注册事件发展历程
简单事件绑定–bind事件绑定–live事件绑定–delegate事件绑定–on事件绑定(推荐),对应的解除监听的函数分别是unbind、die、undelegate、off。
//1.简单注册
$(ele).click(handler) //单击事件
//2.bind方式注册事件
// 第一个参数:type:事件类型,如click、change、mouseover等;
// 第二个参数: data:传入监听函数的参数,通过event.data取到。可选;
// 第二个参数:监听函数,可传入event对象,这里的event是jQuery封装的event对象,与原生的event对象有区别,使用时需要注意
$('ele').bind('click mouseenter',[data] function(){
// 事件响应方法
});
//3.live事件绑定
// 第一个参数:type:事件类型,如click、change、mouseover等;
// 第二个参数: data:传入监听函数的参数,通过event.data取到。可选;
// 第二个参数:监听函数,可传入event对象,这里的event是jQuery封装的event对象,与原生的event对象有区别,使用时需要注意
$('ele').live('click mouseenter',[data] function(){
// 事件响应方法
});
//4.delegate注册委托事件
// 第一个参数:selector,要绑定事件的元素
// 第二个参数:事件类型
// 第三个参数:事件处理函数
$('.parentBox').delegate('p', 'click', function(){
// 为 .parentBox下面的所有的p标签绑定事件
});
我们现在知道绑定事件的具体语法了,我们来谈一下他们之间的区别:
-
bind的特点就是会把监听器绑定到目标元素上,有一个绑一个,在页面上的元素不会动态添加的时候使用它没什么问题。但如果列表中动态增加一个“列表元素5”,点击它是没有反应的,必须再bind一次才行。要想不这么麻烦,我们可以使用live。
-
live利用了事件委托机制来 完成事件的监听处理,把节点的处理委托给了document。在监听函数中,我们可以用event.currentTarget来获取到当前捕捉到事件的 节点。
-
delegate注册委托事件参数多了一个selector,用来指定触发事件的目标元素,监听器将被绑定在调用此方法的元素上
bind | live | delegate | on |
---|---|---|---|
可以同时绑定多个事件 | 支持动态绑定 | 可以委托到就近的父元素上 | 统一了所有 |
但是不支持动态绑定 | 但是所有的事件都委托到了document,给其负担太大 | 但是写法太冗余,我不需要委托怎么办 | 官方推荐 |
4.2 on注册事件
on及支持动态事假的绑定页支持事件的委托,也是上面绑定事件的方法内部也是通过on语法来实现的.
on注册事件的语法:
// 第一个参数:events,绑定事件的名称可以是由空格分隔的多个事件(标准事件或者自定义事件)
// 第二个参数:selector, 执行事件的后代元素(可选),如果没有后代元素,那么事件将有自己执行。
// 第三个参数:data,传递给处理函数的数据,事件触发的时候通过event.data来使用(不常使用)
// 第四个参数:handler,事件处理函数
$(selector).on(events[,selector][,data],handler);
// 表示给$(selector)绑定代理事件,当必须是它的内部元素span才能触发这个事件,支持动态绑定
$(selector).on( 'click','span', function() {});
4.2.1 on能事件委托的原理
// on事件委托的原理
var ul = document.querySelector('#ul');
ul.onclick = function (e) {
// console.log(e.target.tagName);
if (e.target.tagName.toLowerCase() === 'li') {
console.log(e.target);
}
}
4.3 解绑事件
//解绑事件
$(selector).off();
注意点:
*1.如果不传参数就是解绑所有事件
*2.如果传参数就是解绑指定事件
// event.stopPropagation() 阻止事件冒泡行为
// event.preventDefault() 阻止浏览器默认行为
// return false:既能阻止事件冒泡,又能阻止浏览器默认行为。
4.4 事件的触发
$(selector).click(); // 触发 click事件
$(selector).trigger('click');