事件处理函数

说到事件处理函数那得先知道什么是「事件」。
事件简单理解就是干某一件事,比如我吃饭、睡觉这就是一个事件。
既然有了「事件」后总得反馈我点什么。比如我睡觉了第二天我的状态就会很好,我吃饭了我就不会挨饿。
程序毅然如此,我触发「事件」后应该用「事件处理函数」干点什么。
「事件」是元素本身就具有的能力,而绑定事件是给程序一个处理事件的函数,而不是绑定事件本身,被绑定事件的元素就是「事件源」。

绑定事件的方式:
1、利用元素的属性

  1. <div onclick="console.log(1)"></div>
  2. <button onclick="handleClickDiv()"></button>

2、用**on**事件来绑定

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. oDiv.onclick = function(){
  3. // do...
  4. }

在同一元素同一事件的时候,后绑定事件会覆盖前绑定的事件:

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. oDiv.onclick = function(){
  3. console.log(1)
  4. }
  5. oDiv.onclick = function(){
  6. console.log(2)
  7. }
  8. // 点击 div 元素只会打印 2

3、使用**el.addEventListener(type, fn, Boolean)** :::danger 📌 这样虽然是W3C提出的规范,但是IE8浏览器不兼容 :::

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. oDiv.addEeventListener("click",function(){
  3. console.log(1);
  4. },false)

该方法在同一个元素绑定多个同样 的事件依然生效

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. oDiv.addEeventListener("click",function(){
  3. console.log(1);
  4. },false)
  5. oDiv.addEeventListener("click",function(){
  6. console.log(2);
  7. },false)
  8. // 点击的时候会打印 1 2,因为多个同样的事件不会覆盖

但是有一种情况比较特殊:

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. oDiv.addEventListener("click", test, false);
  3. oDiv.addEventListener("click", test, false);
  4. function test() {
  5. console.log(1);
  6. }

这样的情况下两个点击事件绑定的是同一个函数的引用时只会执行一次!!!

4、**IE8**绑定事件的方式

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. oDiv.attachEvent("onclick",function(){
  3. console.log("1");
  4. })

最后来个兼容的写法:

  1. function addEvent(el, type, fn) {
  2. if (el.addEventListener) {
  3. el.addEventListener(type, fn, false);
  4. } else if (el.attachEvent) {
  5. el.attachEvent("on" + type, function () {
  6. fn.call(el);
  7. });
  8. } else {
  9. el["on" + type] = fn;
  10. }
  11. }
  12. var oDiv = document.getElementsByTagName("div")[0];
  13. addEvent(oDiv, "click", function () {
  14. console.log(1);
  15. })

解除事件处理函数

某些情况下我们绑定事件处理函数后,需要解除应该怎么解除呢?

on事件解除事件处理函数

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. oDiv.onclick = function(){}
  3. oDiv.onclick = null;

addEventListener解除事件处理函数
需要注意的是removeEventListener的第一、三个参数必须和addEventListener是一样的,第二个参数表示函数的引用,指向同一个函数引用即可解除。

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. function test(){
  3. console.log(1)
  4. }
  5. oDiv.addEventListener("click", test, false);
  6. oDiv.removeEventListener("click", test, false);
  1. var oDiv = document.getElementsByTagName("div")[0];
  2. // 利用 arguments.callee 来指向同一个函数引用
  3. oDiv.addEventListener("click", function(){
  4. console.log(1)
  5. oDiv.removeEventListener("click", arguments.callee, false);
  6. }, false);

解除IE8attachEvent事件处理函数

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. function test(){}
  3. oDiv.attachEvent("onclick", test);
  4. oDiv.detachEvent("onClick", test)

事件处理函数中的 this

:::info 在事件绑定处理函数中,普通的on事件和addEventListener中的this都指向元素本身,而IE8中的attachEvent指向window :::

  1. var oDiv = document.getElementsByTagName("div")[0];
  2. oDiv.onclick = function(){
  3. console.log(this); // <div></div>
  4. }
  5. // ==========
  6. oDiv.addEventListener("click",function(){
  7. console.log(this); // <div></div>
  8. },false)
  9. // ==========
  10. oDiv.attachEvent("onclick",function(){
  11. console.log(this); // window
  12. })

事件冒泡

先来看下案例的DOM结构:

  1. <!-- 其他内容忽略 -->
  2. <style>
  3. .wrapper {
  4. width: 300px;
  5. height: 300px;
  6. background-color: green;
  7. }
  8. .outer {
  9. width: 200px;
  10. height: 200px;
  11. background-color: red;
  12. }
  13. .inner {
  14. width: 100px;
  15. height: 100px;
  16. background-color: orange;
  17. }
  18. </style>
  19. <!-- 其他内容忽略 -->
  20. <body>
  21. <div class="wrapper">
  22. <div class="outer">
  23. <div class="inner"></div>
  24. </div>
  25. </div>
  26. </body>

image.png
这样的布局形成了三个大小嵌套的div,我想当我点击某个div的时候就打印这个div

  1. var wrapper = document.getElementsByClassName("wrapper")[0];
  2. var outer = document.getElementsByClassName("outer")[0];
  3. var inner = document.getElementsByClassName("inner")[0];
  4. wrapper.addEventListener("click",function () {
  5. console.log("wrapper");
  6. },false);
  7. outer.addEventListener("click",function () {
  8. console.log("outer");
  9. },false);
  10. inner.addEventListener("click",function () {
  11. console.log("inner");
  12. },false);

image.pngimage.pngimage.png
可以看到当我点击黄色div的时候红色、绿色的div点击事件也被触发了,当我点击红色div的时候绿色的div点击事件也被触发了。 :::info 像这样的事件一层一层的向上触发我们就叫做「事件冒泡」。
点击子元素的时候向上冒泡,这个事件要向上传递,直到传递到顶层的父元素停止!!!
事件冒泡一般发生在DOM是嵌套关系的事件中!!!

focusblurchangesubmitresetselect这些事件都不会冒泡!!! ::: image.png

事件捕获

那什么又是事件捕获呢? :::info 「事件捕获」和事件冒泡刚好相反,「事件捕获」是从父元素的事件向下传递,一直到最后的子元素停止,这样的现象我们叫做「事件捕获」。 ::: 当我们使用addEventListener的时候将第三个参数更改为true即表示「事件捕获」,而默认的false表示「事件冒泡」。
image.png

  1. var wrapper = document.getElementsByClassName("wrapper")[0];
  2. var outer = document.getElementsByClassName("outer")[0];
  3. var inner = document.getElementsByClassName("inner")[0];
  4. wrapper.addEventListener("click", function () {
  5. console.log("wrapper");
  6. },true);
  7. outer.addEventListener("click", function () {
  8. console.log("outer");
  9. },true);
  10. inner.addEventListener("click", function () {
  11. console.log("inner");
  12. },true);

image.pngimage.pngimage.png
当点击黄色div的时候会从绿色的div向下捕获直到黄色div停止,当点击红色div的时候会从绿色的div向下捕获直到红色div停止。

事件流的过程

事件捕获和事件冒泡的执行过程时,事件捕获先比事件冒泡执行。

  1. var wrapper = document.getElementsByClassName("wrapper")[0];
  2. var outer = document.getElementsByClassName("outer")[0];
  3. var inner = document.getElementsByClassName("inner")[0];
  4. wrapper.addEventListener("click", function () {
  5. console.log("wrapper 1");
  6. },true);
  7. outer.addEventListener("click", function () {
  8. console.log("outer 2");
  9. },true);
  10. inner.addEventListener("click", function () {
  11. console.log("inner 3");
  12. },true);
  13. inner.addEventListener("click", function () {
  14. console.log("inner 4");
  15. },false);
  16. outer.addEventListener("click", function () {
  17. console.log("outer 5");
  18. },false);
  19. wrapper.addEventListener("click", function () {
  20. console.log("wrapper 6");
  21. },false);

image.png
DOM事件流中,实际的目标(<div>元素)在捕获阶段不会接收到事件。这是因为捕获阶段从document<html>再到<body>就结束了。下一阶段,即会在<div>元素上触发事件的“到达目标” 阶段,通常在事件处理时被认为是冒泡阶段的一部分。然后,冒泡阶段开始,事件反向传播至文档。
image.png

阻止事件冒泡

如何阻止事件冒泡?事件处理函数中有个event事件对象参数,该对象是事件发生时的一些信息,该对象有个**stopPropagation()**方法可以阻止事件冒泡。
IE8event事件对象存在于window上而非事件处理函数中,所以要考虑兼容。

  1. inner.addEventListener("click", function (event) {
  2. // 兼容IE8
  3. var e = event || window.event;
  4. e.stopPropagation();
  5. console.log("bubbleInner");
  6. }, false);

这样就可以阻止事件冒泡!!!

IE8还可以利用event.cancelBubble属性来阻止冒泡。

  1. inner.addEventListener("click", function (event) {
  2. // 兼容IE8
  3. var e = event || window.event;
  4. e.cancelBubble = true;
  5. console.log("bubbleInner");
  6. }, false);

阻止默认事件

浏览器的默认事件有很多种,例如鼠标右键显示菜单等等
取消默认事件有两种方式:
1、函数返回false

  1. document.oncontextmenu = function(){
  2. console.log(1)
  3. return false
  4. }

但是这样的方式无法在addEventListener上使用!!!

2、W3C规范的event.preventDefault()

  1. document.addEventListener("contextmenu",function(event){
  2. var event= event || window.event
  3. event.preventDefault()
  4. },false)

3、IE8以下使用event.returnValue

  1. document.addEventListener("contextmenu",function(event){
  2. var event= event || window.event
  3. event.returnValue = false
  4. },false)