如何绑定事件处理函数
1)ele.onXXX = function(event){}
先写一个DIV:
<div style="width:100px;height:100px;background-color:red;"></div>
再写个JS选中这个DIV,并给他绑定一个 onclick 事件;其中的 this 指向 DOM自身
let div = document.getElementsByTagName('div')[0];
div.onclick = function(){
this.style.backgroundColor = 'green';
}
特点1:兼容性很好,但是一个元素的同一个事件上只能绑定一个事件;
div.onclick = function(){
console.log('a');
}
div.onclick = function(){
console.log('b');
}
这种写法是会被覆盖的,因为这等同于表达式赋值;
特点2 :基本等同于写在 HTML 行间上;
<div style="width:100px;height:100px;background-color:red;" onclick="console.log('a')"></div>
这种写法叫 “句柄”方式,在行间不用写 function ,直接写代码;
2)obj.addEventListener(type,fn,false)
这是W3C的标准写法;其中的 this 指向 DOM自身;
div.addEventListener('事件类型',处理函数,false)
点击事件:
div.addEventListener('click',function(){
console.log(this); // 这里的 this 就是DOM本身;
},false);
或输出点内容;
div.addEventListener('click',function(){
console.log('我点了div');
},false);
特点1:可以给一个对象绑定多个事件,并且按照绑定的先后顺序执行;
div.addEventListener('click',function(){
console.log('a');
},false);
div.addEventListener('click',function(){
console.log('b');
},false);
注意:当同一个函数不能被绑定多次;
div.addEventListener('click',test,false);
div.addEventListener('click',test,false);
function test(){
console.log('a');
}
这种写法就不对了,除了考试,一般人也不会这么写;
IE9以下不兼容,可以为一个事件绑定多个处理函数;
3)obj.attachEvent('on'+type,fn)
IE独有,一个事件同样可以绑定多个处理程序,chrome 不支持,不演示了;
div.attachEvent('onclick',function(){});
练习
<li>1</li>
<li>2</li>
<li>3</li>
这里3个li,要求给每个li绑定事件,并输出编号:
var liCol = document.getElementsByTagName('li');
for (var i = 0; i < liCol.length; i++) {
(function (i) {
liCol[i].addEventListener('click', function () {
console.log(i);
}, false);
}(i));
}
这是以前的写法,闭包,在 addEventListener 外层套个立即执行函数,把参数 i 传进去;
学了ES6之后,使用 let 声明变量就解决问题了;
let liCol = document.getElementsByTagName('li');
for(let i = 0 ; i< liCol.length;i++){
liCol[i].addEventListener('click',function(){
console.log(i);
});
}
封装兼容函数
写个兼容IE的事件绑定函数:现在基本用不上着这样了,知道一下就行;
function addEvent(elem,type,handle){
if(elem.addEventListener){
elem.addEventListener(type,handle,false);
}else if(elem.attachEvent){
elem.attachEvent('on'+type,function(){
handle.call(elem);
});
}else{
elem['on'+type] = handle;
}
}
解除事件绑定
ele.onclick = null;
还是上面的DIV:
<div style="width:100px;height:100px;background-color:red;"></div>
给他绑定事件:
let div = document.getElementsByTagName('div')[0];
div.onclick = function(){
console.log('绑定了 onclick 事件');
}
解除绑定:
div.onclick = null;
如果,事件绑定只是一次性的话,可以直接写在绑定函数里;
let div = document.getElementsByTagName('div')[0];
div.onclick = function(){
console.log('绑定了 onclick 事件');
this.onclick = null;
}
ele.removeEventListener(type,fn,false);
还用上面说的 DIV,给他绑定监听事件;
let div = document.getElementsByTagName('div')[0];
div.addEventListener('click',function(){
console.log(this);
},false);
这种把处理函数写在里面匿名的方式是没法解除绑定的,只能写在外面才可以解除;
let div = document.getElementsByTagName('div')[0];
div.addEventListener('click',demo,false);
function demo(){ // 事件处理函数写在外面
console.log('绑定了事件');
}
div.removeEventLinstener('click',demo,false); // 解除绑定跟绑定写法保持一致;
ele.detachEvent('on'+type,fn);
IE这个,已经不用了,知道就行;
div.attachEvent('onclick',function(){}); // 绑定事件
div.detachEvent('onclick',function(){}); // 解除绑定
事件处理模型
什么是事件处理模型,就是当前元素在发生事件时的处理方式;
先个HTML结构,分别定义了点样式;
<style>
.wrapper{width: 300px; height: 300px; background-color: red;}
.content{width: 200px; height: 200px; background-color: green;}
.box{width: 100px; height: 100px; background-color: orange;}
</style>
<div class="wrapper"> <!--最外层 红色-->
<div class="content"> <!--中间层 绿色-->
<div class="box"></div> <!--最内层 桔色-->
</div>
</div>
长这样的:视觉上桔色块在最上面,其实从结构上来看,桔色块(box)在里内层,以结构层次为准;
JS里,分别选出来,再分别绑定上事件;
var wrapper = document.getElementsByClassName('wrapper')[0];
var content = document.getElementsByClassName('content')[0];
var box = document.getElementsByClassName('box')[0];
wrapper.addEventListener('click',function(){
console.log('wrapper');
},false);
content.addEventListener('click',function(){
console.log('content');
},false);
box.addEventListener('click',function(){
console.log('box');
},false);
点了谁,就在控制台显示谁的名字,注意触发的顺序;
当点了最外层红色的,会触发:红色的 wrapper;
当点了中间层绿色的,会触发:绿色的 content 和红色的 wrapper;
当点了最内层桔色的,会触发:桔色的 box、绿色的 content 和红色的 wrapper;
这种:事件从最内层向最外层联动触发的现象叫事件冒泡;
事件冒泡
结构上(非视觉上的)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,自子元素冒泡向父元素。(自底向上或由内向外)
之所以说是结构上的,而非视觉上的,可以改下样式,让这些块完全错开:
<style>
.wrapper{width: 300px; height: 300px; background-color: red;}
.content{width: 200px; height: 200px; background-color: green;margin-left:300px;}
.box{width: 100px; height: 100px; background-color: orange;margin-left:200px;}
</style>
<div class="wrapper"> <!--最外层 红色-->
<div class="content"> <!--中间层 绿色-->
<div class="box"></div> <!--最内层 桔色-->
</div>
</div>
结果成这样了,视觉上不再嵌套了:
这时,点最内层桔色的,还是会触发:桔色的box、绿色的content和红色的wrapper;
这样就能明白,事件冒泡是基于HTML结构上的了;
点了最内层的块,中间层和最外层的块上绑定的事件也都会触发;所以说是自底向上,或自内向外;
除了事件冒泡,自然还有事件捕获了。
事件捕获
结构上(非视觉上)嵌套关系的元素,会存在事件捕获的功能,即同一事件,自父元素捕获至子元素(事件源元素)。(自顶向下或由外到内),另:IE没有捕获事件;
一个元素对象的同一绑定事件,同时只能存在一种事件类型,即:要么冒泡要么捕获;
事件捕获的方向跟冒泡是相反的,怎么触发捕获呢?
只要把监听事件的第三个参数,改成 true 就可以了,他们的事件处理类型就从冒泡变捕获了;
这第三个参数的意思是:是否捕获? true 为捕获,默认为 false 不捕获,那就是冒泡了;
wrapper.addEventListener('click',function(){
console.log('wrapper');
},true);
content.addEventListener('click',function(){
console.log('content');
},true);
box.addEventListener('click',function(){
console.log('box');
},true);
当点了最外层红色的,会触发:红色的 wrapper;
当点了中间层绿色的,会触发:红色的 wrapper 和绿色的 content;
当点了最内层桔色的,会触发:红色的 wrapper、绿色的 content 和桔色的 box;
这种:事件从最外层向最内层联动触发的现象叫事件捕获;
即使是各块完全错开,点最内层桔色的,还是会触发3个事件,只是顺序变成了:
红色的 wrapper、绿色的 content 和桔色的 box;
触发顺序
事件触发的顺序:先捕获、后冒泡;
假设 a,b,c 3个DIV的结构是:a 在最外层,b 在中间层,c 在最内层;
<div>a
<div>b
<div>c</div>
</div>
</div>
捕获的过程像射箭:从外层射向内层,每穿过一层,捕获一次;
冒泡的过程像炸弹:从内层炸向外层,每炸过一层,冒泡一次;
绑定多个事件
如果要让一个元素对象,即有捕获又有冒泡该怎么办?
那就在当前元素对象上,绑定两个或多个事件就可以了,比如:即绑定冒泡,又绑定捕获:
wrapper.addEventListener('click',function(){
console.log('红色wrapper 捕获');
},true);
content.addEventListener('click',function(){
console.log('绿色content 捕获');
},true);
box.addEventListener('click',function(){
console.log('桔色box 捕获');
},true);
wrapper.addEventListener('click',function(){
console.log('红色wrapper 冒泡');
},false);
content.addEventListener('click',function(){
console.log('绿色content 冒泡');
},false);
box.addEventListener('click',function(){
console.log('桔色box 冒泡');
},false);
修改监听事件的第三个参数就可以了,为了看清楚,分别加了捕获和冒泡;
这时,点击桔色块,会有什么结果呢?
执行的顺序和预测的结果很契合;跟代码的书写顺序没有关系;
现在把捕获的代码放在冒泡的下面,看看是不是捕获的优先级高;
wrapper.addEventListener('click',function(){
console.log('红色wrapper 冒泡');
},false);
content.addEventListener('click',function(){
console.log('绿色content 冒泡');
},false);
box.addEventListener('click',function(){
console.log('桔色box 冒泡');
},false);
wrapper.addEventListener('click',function(){
console.log('红色wrapper 捕获');
},true);
content.addEventListener('click',function(){
console.log('绿色content 捕获');
},true);
box.addEventListener('click',function(){
console.log('桔色box 捕获');
},true);
再点击桔色块,看结果:没错,捕获就是优先于冒泡;
事件执行
有个问题要说清楚:
上面虽然是三个方块,但点击事件的元素本身,是在执行事件,而不是冒泡或捕获;
另外:
focus, blur, change, submit, reset, select 等事件不冒泡;
注意:focus ,是当获取焦点时,如果是按 tab 键让input获得了焦点,就不会冒泡,但要是点了一下input让他获得焦点时,那就触发 click 的事件,冒泡的不是 focus ,是 click;
取消冒泡
有时冒泡并不是什么好事,所以要阻止它;
比如:在文档上绑定了点击事件,那不论点不点这个 wrapper ,都会触发 document,岂不碍事;
var wrapper = document.getElementsByClassName('wrapper')[0];
wrapper.onclick = function(){
this.style.background = 'gray';
}
document.onclick = function(){
console.log('你闲的啊');
}
如何取消冒泡?
W3C标准 event.stopPropagation(); 但不支持IE9以下版本;
事件对象
在某事件发生时,浏览器会记录当前事件发生的信息,这些信息都存在事件对象 e 中;
var wrapper = document.getElementsByClassName('wrapper')[0];
wrapper.onclick = function(e){
console.log(e);
this.style.background = 'gray';
}
输出这个 e 对象看看:
这个是个鼠标事件的对象,这个对象里面有个 stopPropagation() 方法可以取消冒泡;
所以,把这个方法加上:就可以取消当前事件产生的冒泡;
var wrapper = document.getElementsByClassName('wrapper')[0];
wrapper.onclick = function(e){
e.stopPropagation();
this.style.background = 'gray';
}
如果点其他地方,当然还是会触发 document 的冒泡;
另:IE独有 event.cancelBubble = true;
event.cancelBubble = true;
这么写也管用,chrome 也是支持的;
封装取消冒泡的函数
// 封装取消冒泡函数
function stopBubble(e){
if(e.stopPropagation){
e.stopPropagation();
}else{
e.cancelBubble = true;
}
}
阻止默认事件
默认事件:表单提交,a标签跳转,右键菜单等;
1) return false; 以对象属性的方式注册事件才生效;
document.oncontextmenu = function(){
console.log('a');
return false;
}
2)event.preventDefault(); W3C标准,IE9以下不兼容;
document.oncontextmenu = function(){
console.log('a');
e.preventDefault(); // 这个比较标准
}
3)event.returnValue = false; 兼容IE;
document.oncontextmenu = function(){
console.log('a');
e.returnValue = false;
}
封装阻止默认事件的函数
function cancelHandler(e){
if(e.preventDefault){
e.preventDefault();
}else{
e.returnValue = false;
}
};
应用示例
取消右键菜单
document.oncontextmenu = function(e){
console.log('a');
cancelHandler(e);
}
取消 a 标签跳转
<a href="https://www.51cto.com">www.51cto.com</a>
let a = document.getElementsByTagName('a')[0];
a.onclick = function(){
return false;
}
还可以这样:
<a href="javascript:void(0)">www.51cto.com</a>
事件对象
来看个操作:两DIV嵌套;
<div class="wrapper" style="width:100px;height:100px;background-color:red">
<div class="box" style="width:50px;height:50px;background-color:green"></div>
</div>
var wrapper = document.getElementsByClassName('wrapper')[0];
var box = document.getElementsByClassName('box')[0];
wrapper.onclick = function(e){
var e = e || window.event; // IE浏览器中,e 会失效;会存在 window.event 上,所以做个兼容;
console.log(e);
}
当点击 红色wrapper 时,会触发事件,输出 e;
当点击 绿色box 时,也会冒泡触发事件,输出e ;
事件源对象就是点击到哪个对象触发的事件,这个对象就是事件源对象;
可以通过事件对象 e 中的 srcElement 和 target 两个对象属性来获取事件源对象;
var wrapper = document.getElementsByClassName('wrapper')[0];
var box = document.getElementsByClassName('box')[0];
wrapper.onclick = function(e){
var e = e || window.event;
var target = e.target || e.srcElement; // 写兼容写法
console.log(target);
}
点 红色wrapper 输出 div.wrapper对象;
点 绿色box 输出 div.box 对象;
事件委托
知道这个事件源对象有什么用? 看个事例:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
需求: li 是变化的,要求当点击到 li 上时,输出 li 的 innerText ;
var ul = document.getElementsByTagName('ul')[0];
ul.onclick = function(e){
var e = e || e.window.event;
var target = e.target || e.srcElement;
console.log(target.innerText);
}
神奇的是:当点击 li 时,相对应的 源对象 就会被找到,并输出 innerText ;
利用事件冒泡和事件源对象来处理这种问题的方式叫:事件委托,事件委托的优点很明显;
1)性能,不需要循环所有的元素一个个绑定事件;
2)灵活,当有新的子元素时,不需要重新绑定事件;
事件分类
鼠标事件
click, mousedown, mousemove, mouseup, contextmenu, mouseover, mouseout, mouseenter, mouseleave
用 button 来区分鼠标的按键: 0、1、2;
DOM3标准规定:click 事件只能监听左键,只通过 mousedown 和 mouseup 来判断鼠标键 ;
如何解决 mousedown 和 click 的冲突;