事件是各位前端大佬们工作中避免不了的东西,大家肯定都不陌生。那么大家是否有更为细致的了解事件呢。今天我们一起来探讨探讨,有写的不好或者不对的地方,欢迎各位大佬修正。
事件,就是文档或浏览器窗口发生的一些特定的交互瞬间。也就是说html于javascript脚本之间的交互其实就是通过事件来完成的
那么,想要了解事件,有些概念就必须知道了,分别是:
事件流
事件处理程序
事件对象
我们可以简单的理解为 事件流就是描述从页面中接收事件的顺序
我们都知道有 事件冒泡 和 事件捕获
其实最初 IE 和 Netscape所定义的事件流是不一样
IE 的事件流指的是 事件冒泡
而Netscape得事件流指得是 事件捕获
下面我们来详细看看这两种事件流
即 事件开始由最具体的元素逐级向上传播到最不具体的节点 也就是由内往外传播事件的过程 事件冒泡所有浏览器都支持,故比较常用
即 事件开始由最不具体的元素逐级向下传播到最具体的节点 也就是由外往内传播事件的过程 事件捕获得目的在于 事件到达预定目标之前捕获它
因事件捕获存在浏览器兼容性问题,故一般不推荐用
注意: 事件捕获虽然是Netscape唯一支持的事件流 但是如今大部分主流浏览器以及IE9及以上浏览器都已经支持了
首先 最初DOM0级事件认为 事件流是只存在一个阶段得 那就是事件冒泡
后来 DOM2级事件则规定 事件流分为3个阶段 分别是 事件捕获阶段 处于目标事件阶段 事件冒泡阶段
注意: 在DOM事件流中,实际的目标在捕获阶段是不会接收到事件的,也就是说在捕获阶段,是不会触发目标元素上的事件处理程序的。故目标元素的事件会在 第二个阶段 处于目标阶段发生,并进行事件处理。 并且 目标元素上的事件处理会被看做第三个阶段(事件冒泡阶段) 的一部分
但是: 尽管 DOM2级事件流规定捕获阶段不会涉及到事件目标 但是,IE9及以上以及大多数主流浏览器(Safari、Chrome、Firefox 和 Opera 9.5 以及更高版本)都实现了在捕获阶段触发事件对象上的事件。故最终,目标元素上的事件实际上在捕获阶段 和 冒泡阶段都会被触发
简单理解就是 用来响应某个事件的函数 就叫做事件处理程序
事件处理程序我们可能接触过几种 比如
<body id="app">
<div class="myDiv">
myDiv
<button class="btn">目标元素</button>
</div>
</body>
<script>
var btn = document.querySelector('.btn')
var div = document.querySelector('.myDiv')
var body = document.getElementById('app')
btn.onclick = function () {
console.log('我是目标元素')
}
btn.addEventListener('click', function() {
console.log('我是目标元素')
}, false)
btn.attachEvent('onclick', function() {
console.log('我是目标元素')
})
</script>
那么 这3种直接到底有什么区别呢。下面我们来一一说明
首先,第一种
btn.onclick = function () {
console.log('我是目标元素')
}
这种是属于DOM0级事件处理程序
特点 简捷、所有浏览器都支持 不存在兼容性问题 并且函数内部this始终指向元素本身
DOM2级事件处理程序提供了添加事件以及移除事件两个方法, 它们都接受3个参数,分别是 要处理的事件名 处理事件的函数 以及一个布尔值
btn.addEventListener('click', function() {
console.log('我是目标元素')
}, false)
btn.removeEventListener('click', function() {
console.log('移除事件')
}, false)
注意:
1、第三个参数 默认不传时是false, 当为false时, 表示该事件处理程序在事件冒泡阶段被调用, 当为true时,表示该事件处理程序在事件捕获阶段被调用
如下:我们做同一个操作,那就是用鼠标去点击最内层的btn按钮
当第三个参数为false时
btn.addEventListener('click', function () {
console.log('按钮 被触发了')
}, false)
div.addEventListener('click', function () {
console.log('div 被触发了')
}, false)
body.addEventListener('click', function () {
console.log('body 被触发了')
}, false)
当第三个参数为true时
btn.addEventListener('click', function () {
console.log('按钮 被触发了')
}, true)
div.addEventListener('click', function () {
console.log('div 被触发了')
}, true)
body.addEventListener('click', function () {
console.log('body 被触发了')
}, true)
2、这种方法存在兼容性问题,IE9及以上、Firefox、Safari、Chrome 和 Opera等主流浏览器才支持。
3、用addEventListener此方法添加的事件处理程序只能使用removeEventListener来移除
并且,移除时,传入的参数必须和添加事件处理程序时的参数完全相同(否则将会移除不成功)
那么 什么叫参数完全相同呢,我们来看个例子
btn.addEventListener('click', function() {
console.log('我是目标元素')
}, false)
btn.removeEventListener('click', function() {
console.log('我是目标元素')
}, false) // 没有用
注意 上面例子中,表面上好像添加和删除时的参数是一样的,但是,大家不要忘了,对象和函数是复杂数据类型,任何两个复杂数据类型数据都是不完全相等的。因为它们比较的是指针,只有两个指向同一个内存空间的指针才是完全相等的。
所以,以上两个方法的第二个参数(也就是那个处理函数)其实是不相等的。因为它们是匿名函数
所以,总结出一点
当你添加一个事件处理程序时,是以匿名函数的形式添加的,那么这个事件处理程序将不可移除
那么如何才能正确移除呢,看下面的方法
var handleFunction = function () {
console.log('我是目标元素')
}
btn.addEventListener('click', handleFunction, false)
btn.removeEventListener('click', handleFunction, false) // 可正常移除
最后多一句嘴: 上面我们说过,事件捕获是存在兼容性问题的,故,在不必要的情况下,请尽量将事件处理程序添加到事件流的冒泡阶段。你懂的。。。
IE的事件处理程序同样提供了两个方法
btn.attachEvent('onclick', function () {
console.log('btn 被触发了')
})
btn.detachEvent('onclick', function () {
console.log('btn 被触发了')
})
和addEvenListener以及removeEventListener不同的是:
1、IE的attachEvent 和 detachEvent只有两个参数,第一个是事件名,第二个是处理事件的函数。
2、由于 IE8 及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。也就是说通过此方法添加的事件处理函数,只有事件冒泡,没有事件捕获
3、通过attachEvent和detachEvent方法添加和移除事件时,事件名前面都必须加上单词on
4、IE11以下版本(不包括11,IE11及以上和edge都已不支持)浏览器均支持,其他浏览器都不支持,用时请注意
5、addEventListener 和 removeEventListener 以及DOM0级事件处理程序 btn.οnclick= function() {} 内部的this都是=是指向触发事件的元素的,而attachEvent 和 detachEvent 是始终在全局作用域下运行,故内部this 指向window
btn.attachEvent('onclick', function () {
console.log(this === window) // true
})
btn.detachEvent('onclick', function () {
console.log(this === window) // true
})
var EvenItem = {
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
}
var handler = function (event) {
console.log(event.type)
}
// 使用方式如下
// 添加事件
EventItem.addHandleer(btn, 'click', handler)
// 移除事件
EventItem.removeHandler(btn, 'click', handler)
简单理解为 包含所有与事件相关的信息的对象(包括事件的元素,事件类型,以及其他与事件有关的信息)
在DOM事件触发时,会产生这样一个事件对象event
IE中的事件对象和其他浏览器中的DOM事件对象有点不一样。下面我们分别来细说
虽然对于不同的事件类型来说,事件对象中的属性和方法可能会有不一样的地方。但是不管哪种事件类型的事件对象,都会存在以下属性和方法
我们挑几个常用的来加以说明
这个方法表示可以取消事件的默认行为,前提是必须cancelable属性为true时,调用才能生效
比如:当我们点击一个a标签时,会触发a标签跳转herf属性的url地址这个默认行为。当我们不需要产生跳转时,就可以通过preventDefault()去取消这个默认行为
这个方法表示阻止事件继续传播(可能是阻止事件冒泡,也可能是事件捕获),但前提是bubbles这个属性为true时,调用此方法才能生效
这两者有什么区别呢
currentTarget: 当前正在处理事件的那个元素
target: 事件的目标
咋一看,好像没太明白这两个的区别。没关心,我们看个例子就懂了
<body id="app">
<div class="myDiv">
myDiv
<button class="btn">目标元素</button>
</div>
</body>
<script>
var btn = document.querySelector('.btn')
var div = document.querySelector('.myDiv')
var body = document.getElementById('app')
// 我们做一个操作,那就是用鼠标点击 目标元素 按钮,由于事件冒泡的原因,故会同时触发btn,div,body3个元素的点击事件
btn.onclick = function (event) {
console.log('我是目标元素')
console.log(event.currentTarget) // btn
console.log(event.target) // btn
console.log(event.currentTarget === this) // true
console.log(event.target === this) // true
}
div.addEventListener('click', function(event) {
console.log('我是div')
console.log(event.currentTarget) // div
console.log(event.target) // btn
console.log(event.currentTarget === this) // true
console.log(event.target === this) // false
}, false)
body.onclick = function (event) {
console.log('我是body')
console.log(event.currentTarget) // body
console.log(event.target) // btn
console.log(event.currentTarget === this) // true
console.log(event.target === this) // false
}
</script>
由以上代码以及输出结果我们就可以得出以下结论
event.target 表示的是真正被点击(或者其他操作)的元素 称为事件目标(我们鼠标点击的是 目标元素 那个按钮,故无论冒泡到哪个元素上执行处理程序,target都始终为被鼠标点击的那个 button)
而
event.currentTarget 表示的是当前正在处理事件的元素,故event.currentTarget始终等于this(比如 当前冒泡到body上触发了body的事件处理程序,那么对于这个事件处理程序而言,当前正在处理这个事件的元素就是body 故 event.currentTarget 就是body)
首先,对于IE而言,不同的事件处理程序 event事件对象将有不同的区别
var btn = document.getElementById("myBtn");
btn.onclick = function(){
var event = window.event;
alert(event.type); //"click"
};
尽管如此,但IE9及以上版本的浏览器实际也已经实现了 将event对象作为处理函数的参数返回,具体看下面一段代码
btn.onclick = function (event) {
console.log(event) // undefind IE9以下
console.log(event) // object IE9及以上
console.log(window.event) // object 所有IE浏览器
}
var btn = document.getElementById("myBtn");
btn.attachEvent('onclick', function(event) {
console.log(event) // object 所有支持attachEvent的IE浏览器
console.log(window.event) // object 所有支持attachEvent的IE浏览器 两种方式均可获取event
});
其中,针对srcElement,我稍微做个说明:
srcElement 是和target属性相同的,而不是currentTarget,故srcElement表示的始终是真正被点击(或者其他操作)的目标元素
btn.attachEvent('onclick', function (event) {
console.log('我是目标元素')
console.log(event.srcElement) // btn
console.log(this) // window
console.log(event.srcElement === this) // false
})
div.attachEvent('onclick', function (event) {
console.log('我是div')
console.log(event.srcElement) // btn
console.log(this) // window
console.log(event.srcElement === this) // false
})
body.attachEvent('onclick', function (event) {
console.log('我是body')
console.log(event.srcElement) // btn
console.log(this) // window
console.log(event.srcElement === this) // false
})
虽然 DOM 和 IE 中的 event 对象不同,但基于它们之间的相似性我们是不是也可以拿出跨浏览器的方案来,附上代码
var EvenItem = {
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
getEvent: function (event) {
return event ? event : window.event
},
preventDefault : function (event) {
if (event.preventDefault) {
event.preventDefault()
} else {
event.returnValue = false
}
},
stopPropagation: function (event) {
if (event.stopPropagation) {
event.stopPropagation()
} else {
event.cancelBubble = true
}
}
}
var handler = function (event) {
console.log(event.type)
}