DOM事件的绑定
发布在没事瞎琢磨2015年6月17日view:4999
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

warming time!

猜猜弹几个框?

var test=document.querySelector('.test');
test.addEventListener('click',function(event){
    alert('first click');
},false);
test.addEventListener('click',function(event){
    alert('second click');
},false);
test.addEventListener('mousedown',function(event){
    alert('first mousedown');
},false);

好了!开始边学习边实践。本文定位: 运行环境包括IE9+、chrome、firefox, 不会考虑兼容问题.

事件的绑定

事件的绑定有两种方法:

  • element.on(type)=listener
  • element.addEventListener(type, listener, useCapture) 接下来依次学习这两种方式:

element.on(type)=listener

该方式是DOM0级事件处理方式,早期和html代码混合在一起。类似这样

的形式。其中type为事件类型,这里的意思就是指onclick、onmousedown、ondblclick等. 后面的listener为事件监听函数,该函数接收一个event对象,为该次事件发生时的事件描述对象.

特征

  • 它的本质就是给元素添加相应的属性
  • 它的事件处理程序在执行时,其中的this指向当前的元素
  • 该方式不会做同一元素的同类型事件绑定累加. 也就是当你在同一个元素上多次绑定相同类型的监听函数时,后者会覆盖前者。如果你理解了第一点,那么该条很好理解。

element.addEventListener(type, listener, useCapture)

其中type为事件类型如click, mousedown, mouseover, mousemove等,listener为事件监听函数,该函数接收一个event对象,为该次事件发生时的事件描述对象。我们知道,DOM2级事件中规定的事件流包含三个阶段:捕获阶段、处于目标阶段和冒泡阶段。而useCapture就是指定该事件监听函数是在捕获阶段触发还是在冒泡阶段被触发,考虑到众多浏览器兼容问题,一般使用false。

特征

  • 当用户进行一个操作时,浏览器会根据该操作依次触发相应的事件监听函数. 此时我们暂时不考虑你程序中的人为阻止,如stopPropagation、preventDefault等等的操作
  • 当同一个元素上绑定了多次同类型事件,如test元素上做了多次的click事件,那么遵循’先绑定先触发’的原则.
  • 它的事件处理程序在执行时,其中的this指向当前的元素

到这里,我有个疑问: 第一种方式是作为元素属性存在,所以我说它”有迹可循”。那么第二种方式注册的事件监听器是”放”在了哪里呢?一张页面上有众多的元素,每个元素上都可能绑定有事件监听器。浏览器内部如何管理如此多的监听器呢?

先了解个概念:

EventTarget接口

EventTarget是一个接口,能接收事件并针对这些事件绑有监听器的对象实现了这个接口(接口是抽象的,大家都知道)。如果你去翻浏览器源码,你应该能看到EventTarget这个接口的定义。

DOM元素、document、window是最常见的事件目标(event target), 但是另外的对象也有可能成为事件目标(event target), 例如XMLHttpRequest, AudioNode, AudioContext等. 很多事件目标(包含DOM元素、document、window)也支持通过 on...属性来设置事件监听函数. 翻译自MDN 先明白个概念:能绑事件监听器的对象,是因为它实现了EventTarget接口。这么说吧,因为定义了EventTarget这个接口,DOM就不再是简单的DOM了。

// Introduced in DOM Level 2:
interface EventTarget {
  void               addEventListener(in DOMString type,
                                      in EventListener listener,
                                      in boolean useCapture);
  void               removeEventListener(in DOMString type,
                                         in EventListener listener,
                                         in boolean useCapture);
  boolean            dispatchEvent(in Event evt)
                                        raises(EventException);
};

IE9+ chrome ff实现了该接口

大致也回答下上面的问题:

绑定阶段

在事件绑定阶段,如果遇到事件处理程序,内存中就会增加一个事件处理程序,同时将绑定的元素指向该处理程序,即一个到内存的引用。

触发阶段

在事件触发阶段,先从document开始向下捕获,到目标元素后开始冒泡,一直往上冒泡过程中牵涉到的元素,如果元素上绑定有相应的事件处理程序,那么执行该事件处理程序。 至于怎么知道是哪个元素触发了事件,这个嘛...浏览器自身需要实现的。摘抄自webkit内幕:‘当渲染引擎接收到一个事件的时候,它会通过HitTest(webkit中的一种检查触发事件在哪个区域的算法)检查哪个元素是直接的事件目标’。

老生长谈的例子:

for(var i=0;i<10;i++){
    div.addEventListener('click', function(){...},false);
}

或这样:

var fn=function(){...}
for(var i=0;i<10;i++){
    div.addEventListener('click', fn, false);
}

你说第一种好还是第二种好?还是一样呢? 第一种是每次都会创建一个匿名函数并放在内存中,然后建立div到该函数的回调;而第二种方法则是只创建一次fn,然后存入内存,将要绑定该监听函数的dom指向该函数。所以第二种要优于第一个,这样内存占得少,而且引用也少。

所以呢, 如果一个页面上有很多的事件绑定,那么内存方面是不是也会出现问题?这就要说到事件委托了:

事件委托

事件委托利用事件触发时的冒泡特征,如果一堆元素使用了一样的事件处理程序,那么可以在其上层元素进行事件绑定,那么当事件发生时,当冒泡传递到这个上层元素时,通过检测target,如果符合这一堆元素,那么进行事件处理程序的执行。 事件委托的好处就是: * 代码会整洁很多 * 减少了内存占有,降低了元素到内存的引用树,比如以前要5个引用,现在把这5个合并成了一个绑定,那么就只有一个引用咯

如何在浏览器上查看事件绑定

enter image description here 如上:再chrome的Event Listeners标签中,列出了当前页面的所有的事件监听器。你可以点击上方的漏斗选择查看全部的节点上的监听还是当前选中的节点上的监听。上图中我选择了只查看当前选中的节点,所以我们很清楚地看到了改元素上绑定的事件。通过isAttribute属性,我们可以判断出该事件监听器是通过addEventListener绑定上去的。因为如果使用onclick绑定的话,该值为true。

好了,就这样结束吧~

评论
发表评论
暂无评论
WRITTEN BY
rubyisapm
作为一个前端,尤其是前端妹纸,一定要...优雅~
TA的新浪微博
PUBLISHED IN
没事瞎琢磨

没事瞎琢磨…

友情链接 大搜车前端团队博客
我的收藏