事件处理模型与事件委托

原创
2019/01/17 16:19
阅读数 1.7K

事件处理模型——事件冒泡、事件捕获

上一篇介绍了事件的绑定,我们这里先写一个三层div嵌套的结构并且给每一个div都加一个点击事件。

1.   
2.  .wrapper {
3.      width: 200px;
4.      height: 200px;
5.      background-color: red;
6.  }
7.  .box {
8.      width: 100px;
9.      height: 100px;
10.      background-color: green;
11.  }
12.   
13.  .content {
14.      width: 50px;
15.      height: 50px;
16.      background-color: black;
17.  }
18.   

CSS; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

1

1.   
2.  var wrapper = document.getElementsByClassName(‘wrapper’)[0],
3.      box = document.getElementsByClassName(‘box’)[0],
4.      content = document.getElementsByClassName(‘content’)[0];
5.  wrapper.onclick = function () {
6.      console.log(‘wrapper’);
7.  }
8.  box.onclick = function () {
9.      console.log(‘box’);
10.  }
11.  content.onclick = function () {
12.      console.log(‘content’);
13.  }

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在我们点击最外层的wrapper,控制台打印的wrapper。

点击box,却打印出来box和wrapper

点击content打印出来content、box、wrapper

这个现象就是我们所说的事件冒泡。

什么叫冒泡?

结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,子元素冒泡向父元素,结构上的自底向上。(这里的底是结构上的底,视觉上是自顶向下)

大部分事件都有事件冒泡现象,并且所有的浏览器都有事件冒泡。

结构上的冒泡,和视觉的位置没有关系,我们看一下三个方块视觉上分开的例子:

1.   
2.  .wrapper {
3.          width: 200px;
4.          height: 200px;
5.          background-color: red;
6.          position: absolute;
7.      }
8.      .box {
9.          width: 100px;
10.          height: 100px;
11.          background-color: green;
12.          position: absolute;
13.          left: 200px;
14.          top: 200px;
15.      }
16.   
17.      .content {
18.          width: 50px;
19.          height: 50px;
20.          background-color: black;
21.          position: absolute;
22.          left: 100px;
23.          top: 100px;
24.      }

CSS; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

2

我们点击content部分之后依然会把box和wrapper都打印出来。

并不是所有的事件都有冒泡,focus、blur、change、submit、reset、select等方法就没有事件冒泡现象。

事件捕获:

结构上(非视觉上)嵌套关系的元素,会存在事件捕获功能,即同一事件,自父元素捕获至子元素(事件源元素),结构上的自顶向下。

addEventListener最后一个参数就是是否开始事件捕获,当我们填true的时候,就代表开启了事件捕获。只要开启了事件捕获,就不会冒泡了,如果不捕获的话,就遵循事件冒泡。

因为addEventListener只有chrome有,因此事件捕获也只有chrome浏览器有。

依然是上面的那个例子:

1.   
2.  var wrapper = document.getElementsByClassName(‘wrapper’)[0],
3.      box = document.getElementsByClassName(‘box’)[0],
4.      content = document.getElementsByClassName(‘content’)[0];
5.  wrapper.addEventListener(‘click’, function (e) {
6.      console.log(‘wrapper’);
7.  }, true);
8.  box.addEventListener(‘click’, function (e) {
9.      console.log(‘box’);
10.  }, true);
11.  content.addEventListener(‘click’, function (e) {
12.      console.log(‘content’);
13.  }, true);

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在点击content之后,顺序是wrapper、box、content。

当事件冒泡和事件捕获同时存在的时候,事件冒泡和事件捕获的触发顺序则为:先捕获,再冒泡

1.   
2.  var wrapper = document.getElementsByClassName(‘wrapper’)[0],
3.      box = document.getElementsByClassName(‘box’)[0],
4.      content = document.getElementsByClassName(‘content’)[0];
5.  wrapper.onclick = function () {
6.      console.log(‘wrappeBubbler’);
7.  }
8.  box.onclick = function () {
9.      console.log(‘boxBubble’);
10.  }
11.  content.onclick = function () {
12.      console.log(‘contentBubble’);
13.  }
14.  wrapper.addEventListener(‘click’, function (e) {
15.      console.log(‘wrapper’);
16.  }, true);
17.  box.addEventListener(‘click’, function (e) {
18.      console.log(‘box’);
19.  }, true);
20.  content.addEventListener(‘click’, function (e) {
21.      console.log(‘content’);
22.  }, true);

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

结果是先捕获再冒泡。

但是当我们把捕获写到冒泡前面的时候,顺序好像发生了变化。

wrapper–>box–>contentBubble–>content–>boxBubble–>wrapperBubble

这里是因为点击content,并不属于冒泡,而是属于事件执行,我们先绑定的boxBubble,所以就先捕获,再事件执行,再冒泡,这与我们的结论没有冲突。

取消冒泡和阻止默认事件

有时候冒泡或者默认事件会对我们的功能造成影响,因此我们需要适时地取消冒泡和默认事件。

我们绑定事件的处理函数的时候,可以传递一个形参,代表我们的事件对象,一般是e或者event,系统会自动帮我们捕获事件源对象并且把事件源对象传入。

取消冒泡的方法

1.w3c标准方法:event.stopPropagation()

1.   
2.  var wrapper = document.getElementsByClassName(‘wrapper’)[0],
3.      box = document.getElementsByClassName(‘box’)[0],
4.      content = document.getElementsByClassName(‘content’)[0];
5.  content.onclick = function (e) {
6.      console.log(‘content’);
7.      e.stopPropagation();
8.  }

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在我们点击content之后就没有冒泡了,但是点击box还是有冒泡的,因为我们没有取消box的冒泡。

IE9以及以下的版本不支持这个方法

2.event.cancelBubble = true

这个属性是IE的,不过一些高版本的浏览器也有这个属性,只要让这个属性的值等于true,同样也可以取消事件冒泡。

封装一个兼容性的取消事件冒泡的方法:

1.   
2.   
3.  function stopBubble(event) {
4.      if(event.stopPropagation) {
5.          event.stopPropagation();
6.      }else {
7.          event.cancelBubble = true;
8.      }
9.  }

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

默认事件

当我们在浏览器中点击右键,会弹出一个菜单,这就是一个默认事件contextmenu。还有a标签,即使我们不写跳转的页面,也会自动刷新页面,这也是一个默认事件。

移动端的默认事件更多。

默认事件有好的也有不好的,这就需要我们把不需要的默认事件阻止掉。

阻止默认事件

1.return false

我们只要在处理函数最后写上 return false就可以阻止默认事件了。

1.   
2.  document.oncontextmenu = function () {
3.      console.log(‘menu’);
4.      return false;
5.  }

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在我们在页面上右键就不会出现菜单了。

不过要注意的是,这种写法只能用在句柄方式绑定的事件上。

2.e.preventDefault()

1.   
2.  documet.addEventListener(‘contextmenu’, function (e) {
3.      console.log(‘menu’);
4.      e.preventDefault();
5.  },false);

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这是w3c标准的阻止默认事件的方法,句柄也同样可以使用。

不过IE9以下不兼容

3.e.returnValue = false

这个是IE的方法,事件源对象上的属性returnValue代表是否有默认事件,直接返回false就可以阻止默认事件了。现在高版本的浏览器也有这个属性。

1.   
2.  document.attachEvent(‘oncontextmenu’, function (e) {
3.      e.returnValue = false;
4.  });

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

现在我们也可以封装一个兼容性的阻止默认事件的方法了:

1.   
2.  function cancelHandler(event) {
3.      if(event.preventDefault) {
4.          event.preventDefault();
5.      }else{
6.          event.returnValue = false;
7.      }
8.  }

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

小例子:阻止a标签不跳转

1.   
2.  var a = document.links[0];
3.  a.addEventListener(‘click’, funciton (e) {
4.      e.cancelHandler(e);
5.  },false);

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样a标签就不会跳转了。

同时我们还可以用a标签的第四个用处,协议限定符来阻止默认事件。

1.   
2.  <a href=“javascript: void(0); “>www.baidu.com</a>

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

不仅仅是0,只要填写一个代表false的值,就可以取消掉默认事件。

事件对象

在IE中,系统不会把事件对象传到方法中,因此我们的参数e或者event在IE中是不好用的,IE会把事件对象传递到window.event上,所以当我们使用事件对象的时候,就要写兼容性的写法:

1.   
2.  var event = e || window.event;

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

这样就可以正常地获取到事件对象了。

事件委托

事件源对象

我们现在有一个ul,下面有十万个li,当我们给父级的ul添加第一个点击事件之后,由于事件冒泡的存在,不论我们点击哪一个li都会调用父级的点击事件处理函数,这个时候触发父级ul的点击函数的那个li就被称之为事件源对象。

event.target 是火狐的获取事件源对象

event.srcElement 是IE的获取事件源对象

chrome两种都有

因此我们在获取事件源对象的时候也需要写兼容性写法, 配合刚才的事件对象的兼容性写法就是这个样子的:

1.   
2.  oUl.addEventListener(‘click’, function (e) {
3.      var event = e || window.event;
4.      var tar = event.target || event.srcElement;
5.          console.log(tar);
6.      }, false);

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

我们利用事件源对象和事件冒泡来处理的方式就叫做事件委托。

1.   
2.  oUl.addEventListener(‘click’, function (e) {
3.      var event = e || window.event;
4.      var tar = event.target || event.srcElement;
5.      console.log(tar.innerHTML);
6.      }, false);

JavaScript; “复制代码”); “查看纯文本代码”); “返回代码高亮”)

事件委托的优点:

1.性能 不需要循环所有的子元素一个个绑定事件

2.灵活 当有新的子元素被加入的时候不需要重新绑定事件

展开阅读全文
打赏
1
9 收藏
分享
加载中
更多评论
打赏
0 评论
9 收藏
1
分享
返回顶部
顶部