0
点赞
收藏
分享

微信扫一扫

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类

如何绑定事件处理函数

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)在里内层,以结构层次为准;

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类_事件捕获

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>

结果成这样了,视觉上不再嵌套了:

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类_事件委托_02

这时,点最内层桔色的,还是会触发:桔色的box、绿色的content和红色的wrapper;

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类_事件绑定_03

这样就能明白,事件冒泡是基于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;

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类_事件捕获_04

触发顺序

事件触发的顺序:先捕获、后冒泡;

假设 a,b,c 3个DIV的结构是:a 在最外层,b 在中间层,c 在最内层;

<div>a
<div>b
<div>c</div>
</div>
</div>

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类_事件委托_05

捕获的过程像射箭:从外层射向内层,每穿过一层,捕获一次;

冒泡的过程像炸弹:从内层炸向外层,每炸过一层,冒泡一次;


绑定多个事件

如果要让一个元素对象,即有捕获又有冒泡该怎么办?

那就在当前元素对象上,绑定两个或多个事件就可以了,比如:即绑定冒泡,又绑定捕获:

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);

修改监听事件的第三个参数就可以了,为了看清楚,分别加了捕获和冒泡;

这时,点击桔色块,会有什么结果呢?

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类_事件冒泡_06

执行的顺序和预测的结果很契合;跟代码的书写顺序没有关系;

现在把捕获的代码放在冒泡的下面,看看是不是捕获的优先级高;

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);

再点击桔色块,看结果:没错,捕获就是优先于冒泡;

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类_事件冒泡_07

事件执行

有个问题要说清楚:

上面虽然是三个方块,但点击事件的元素本身,是在执行事件,而不是冒泡或捕获

​另外:

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 对象看看: 

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类_事件捕获_08

这个是个鼠标事件的对象,这个对象里面有个 stopPropagation() 方法可以取消冒泡;

Javascript(笔记47) - 事件 - 绑定事件、事件冒泡、事件捕获、事件委托、事件分类_事件分类_09

所以,把这个方法加上:就可以取消当前事件产生的冒泡;

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 的冲突;





举报

相关推荐

0 条评论