JavaScript之事件机制
转:事件机制
一、事件机制环节
事件机制分为四个环节:
- 事件流(冒泡、捕获)
- 事件处理程序
- 事件对象
- 事件委托
二、事件流
事件流:指从页面中接收事件的顺序,有冒泡流和捕获流。
当页面中发生某种事件(比如鼠标点击,鼠标滑过等)时,毫无疑问子元素和父元素都会接收到该事件,可具体顺序是怎样的呢?冒泡和捕获则描述了两种不同的顺序。
那么冒泡和捕获十一什么样的形式发生的呢?接下来,一张图,帮助你来理解:
你想一下,js的语言特点是什么就知道了,其中一个特点就是事件机制。这样你就知道,这个事件的重要性了,这也是面试老生常谈的话题。
事件是每一个浏览器本来就有的,我们只是给相应的事件添加了一个回调函数。这样在触发一个函数之后,相应的会进行调用回调函数。那么该有疑问了,事件是用来做什么的,那么用一个小demo来感受一下,这个事件是用来做什么用的。
div.addEventListener('mousedown', function (e){
var disX = e.clientX - parseInt(getStyle(this, 'left')),
disY = e.clientY - parseInt(getStyle(this, 'top'));//这是获取鼠标点跟元素最左边和最上边的距离
document.addEventListener('mousemove', mouseMove, false);//用document是有原因的,加入你拖动的比较快,会脱离这个元素,如果你用元素的话,这个元素会停止。
div.addEventListener('mouseup', function (e) {
document.removeEventListener('mousemove', mouseMove, false);
}, false);
}, false);
function mouseMove(e) {
div.style.left = e.clentX - disX + 'px';//
div.style.top = e.clientY - disY + 'px';//必须要减去diX、diY,这样这个鼠标点才会在你指定的地方。
}
这只是一个简单的拖拽的例子。里面用到了的事件有onmousedown、onmousemove、onmouseup.这三个兄弟基本是连体的,要用一起用。
三、事件处理程序
事件的绑定
- 句柄方式
var div = document.getElementsByTagName('div')[0];
div.onclick = function (e) {
console.log('a');
}
div.onclick = function(){
console.log("b")
}
后面的这个函数就叫做事件处理函数,也称回调函数,当我们点击事件触发的时候,就会执行后面的处理函数。
事件可以持续监听,并不是执行完一次就失效,因此这个事件监听部分并不属于js引擎,因为js引擎是单线程的,事件监听属于内核的其他模块部分,一旦事件触发,事件监听就会把处理函数放入执行队列,等待js引擎来执行。
虽然句柄方式的兼容性很好,但是一个元素的一种事件只能绑定一个函数。
如果用这种方式,绑定多个事件的话,前面的事件就会被覆盖掉的,所以还是相当于就绑定了一个事件。虽然说有好处,但是缺点要比好大的多。
这种方式也可以写在行间样式上面:
<div onclick="console.log('a')"></div>
或者里面写一个函数名,在js中把这个函数给定义出来,也相当于一个执行队列,等待着被调用。
- ele.addEventListener(type, handle, false)方法
div.addEventListener('click', function(e) {
console.log('a');
}, false);
这里面有三个参数,第一个参数是事件类型,第二个参数是处理函数,第三个参数是是否捕获。
当第三个对象为true时,则就变成了捕获。
处理函数可以直接在addEventListener方法里面写一个匿名函数,也可以在外面写一个命名函数,然后在放法里面写函数的引用。
这种方法更加通用常见,而且一种事件可以绑定多个函数,但是同一个处理函数只能绑定一次。
function test1 () {
console.log('a');
}
function test2() {
console.log('a');
}
div.addEventListener('click', test1, false);
div.addEventListener('click', test2, false);
这样的话,这两个函数都会执行,也不会被覆盖的。
当然它也是唯一一个可以进行捕获的事件绑定的方式。但是兼容性的问题,IE9以下是不兼容的。
- ele.attachEvent(‘on’ + type, handle)方式
这种方式是IE独有的一种方式,同样也是可以进行绑定多个回调函数的。
div.attachEvent('onclick', function (){
console.log('a');
});
基本和addEventListener差不多,但是有一点区别是,当同一个函数绑定多次的时候,addEventListener是只执行一次,但是attachEvent会绑定几次执行几次。
function test () {
console.log('a');
}
div.attachEvent('onclick', test);
div.attachEvent('onclick', test);
这样回打印两个a。每一种方式还是有一点不同的,所以那一种我们都必须得学习。
接下来,用的比较的多的,也是特别使用的是,在这三个时间处理中的this指向是很重要的。
-
句柄绑定方式中,函数里面的this指向元素本身。
-
addEventListener方式中,函数里面的this也是指向元素本身。
-
attachEvent中,函数里面的this指向的是window而不是元素本身,这算是IE的一个BUG。针对这种情况,我们就需要把函数提取出来,然后在attachEvent的时候用call来改变函数内部this的指向。
div.attachEvent('onclick', function () {
test.call(div);
}, false);
有了这些作为知识点,那样的话,我们就可以封装一个函数,来做一下兼容处理:
function attachEvent(ele, type, handle) {
if (ele.addEventListener) {
ele.addEventListener(type, handle, false);
}else if (ele.attachEvent) {
ele.attachEvent('on' + type, function () {
handle.call(ele);
});
}else {
ele['on' + type] = handle;
}
}
这样还是存在一个小问题,因为第二种方法引用了一个匿名函数,这样我们就解除不了绑定了,所以,我们得把这个匿名函数变成“有名”的不就行了吗。接下来进行优化:
function attachevent(ele, type, handle){
if(ele.addEventListener){
ele.addEventListener(type, handle, false)
}
else if(ele.attachEvent){
ele['similate' + type + handle] = handle;//把这个函数给保存起来
ele[type + handle] = function(){//给匿名函数一个名字,这样就能找到
ele['similate' + type + handle].call(ele);//改变一下this的指向
}
ele.attachEvent('on' + type,ele[type + handle])//这样的绑定就可以解除了
}
else{
ele['on' + type] = handle;
}
}
这里专门处理了IE方法中的匿名函数问题,我们用元素自身的一个属性来保存了这个处理函数。这样才是最完整的一个方法了,也是经常用的一个方法了。
有绑定,就会有解除绑定的
事件的解绑
- 句柄方式
ele.οnclick=null
这样很简单的就可以解除绑定的事件处理函数了。
- ele.removeEventListener(type, handle, false)
针对的addEventListener的解除绑定。
但是这里要注意,只有命名函数才可以解除绑定,当绑定的函数是匿名函数的时候,是没有办法解除绑定的。
div.addEventListener('click', function (){console.log('a');}, false);
div.removeEventListener('click', function (){console.log('a');}, false);
这种形式是解除不了绑定的,刚才封装方法的时候,就特别进行优化的。
- ele.detachEvent(‘on’ + type, handle)
针对IE的attachEvent的解除绑定。
如果不想改变this指向问题,这就无所谓了,但是想要改变this指向,就一定要注意一下了。
封装解除绑定的方法:
function remvoeEvent(ele, type, handle) {
if(ele.removeEventListener) {
ele.removeEventListener(type, handle, false);
}else if (ele.detachEvent) {
ele.detachEvent('on' + type, handle);
}else {
ele['on' + type] = null;
}
}
四、事件委托
事件委托是指将事件绑定在父元素上,然后采用事件冒泡的方法,当事件流达到父元素时,可以通过target获取真正触发的当前元素,并执行绑定在父元素上的方法。
这样做可以省去一个个给子元素绑定事件。
一般的步骤是:
①确定需要监听时间的父元素,我现在一般需要画出DOM树
②给父元素添加addEventListener(‘event’, function)
③通过父元素.target获取实际被操作的元素,在函数中进行处理
通俗点说,就是先找到一个比较高的公共元素节点,给它绑定事件,然后通过event.target知道是哪个子节点触发了事件,找到了触发节点,就可以对它进行操作(删除它,删除它的父节点等)