事件委托、函数节流及防抖

一、事件委托:
  • JS事件处理程序的性能缺陷:

由于事件处理程序可以为现代web应用程序提供交互能力,因此许多开发人员会部分青红皂白的向页面中添加大量的处理程序。在创建GUI的语言(比如C#)中,为GUI中的每一个按钮添加一个onclick事件处理程序是司空见惯的事情,而且这样做也不会导至什么问题。可是在js中,添加到页面上的事件处理程序都会占用内存,内存占用的越多性能就越差,且必须事先就指定好所有的事件处理程序而导致的DOM访问次数的增加会延迟整个页面的交互就绪时间。 
————《JavaScript高级程序设计(三版)》

我想JS高程中对于事件处理程序中的问题说的很清楚了,基本上就是说事件处理程序绑定的越多越影响性能,但是又不能说不绑定事件,所以我们需要一种方法来减少绑定的事件。

  • 事件对象:

在触发DOM上的某个事件的时候,就会产生一个事件对象event,这个对象中包含着所有与事件有关的信息,其中包括导至事件的元素、事件的类型以及其他与特定事件相关的信息。

兼容DOM的浏览器会将一个event对象传入到事件处理程序中,无论指定事件处理程序时使用什么方法,都会传入一个event对象:

var btn = document.getElementById('myBtn');
btn.onclick.function (event) {
    alert(event.type);      //'click'
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

上面的事件处理程序会弹出一个警告框,框中显示有event.type属性表示的事件类型。

当然,event对象上包含很多特定的属性和方法,这里只列出一个我们会用到的属性,那就是target属性,这个属性表明了我们的事件的目标。

当然,上面说的是通用的现代浏览器的event对象,当这个对象存在与IE中的时候,又不一样了:

在使用DOM0级方法添加事件处理程序的时候,event对象是作为window对象的一个属性存在的,看下面的例子:

var btn = document.getElementById('myBtn');
btn.onclick.function () {
    var event = window.event;
    alert(event.type);      //'click'
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

如上面所见,我们通过window.event取得了event对象,并检测了被触发事件的类型。

那么在IE中的target属性又是什么样的呢?其实差不多,不过是target属性被替换成了srcElement属性来表示事件的目标,但是因为事件处理程序的作用域是根据指定它的方式来确定的,因此不能认为this会始终等于时间目标,所以最好使用window.event.srcElement来最终确定事件的目标!

因此,一个兼容IE的事件目标的函数如下:

oUl.onclick = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    // 程序
 }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 事件委托:

什么是事件委托呢?书上的原话是:“事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件”,简单点解释的话,就是假如我们有一个列表,如下:

<ul>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
</ul>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果需要每个li点击之后display为none,那么传统的做法是用一个for循环遍历每一个li节点,然后在循环里面给每一个li都绑定上一个click事件;

而使用事件委托呢?就是给ul添加一个onclick的事件处理程序。。。看到这里或许就要问了,我给ul添加点击事件,那岂不是在ul里面随便点击一下就会触发了?确实如此,所以这里要进行很重要的一步:判断我们点击的到底是什么,然后再根据我们获知的点击对象去执行对应的函数。

那么如何去判断我们点击了什么呢?

首先要用到一个我们平时在绑定事件的时候都会禁止掉的一个东西:事件冒泡。试想一下如果ul和li上都绑定了点击事件,同时li上绑定的事件没有禁止掉事件冒泡,那么当我们点击li的时候就会先触发li上的点击事件然后再触发ul上的点击事件了。同理,我们可以通过冒泡来获取click事件再执行ul上的事件处理程序。

其次,我们需要判断冒泡上来的事件流是否是在li上面触发的,如果随便一个都能冒泡并触发ul上的点击事件那也没什么意义了。这里就要用到我们上面提到的事件目标对象的获取了,通过检测我们获取到的目标的nodeName,看其是不是li就可以了。

最终的事件委托代码如下:

var oUl = document.getElementById("ul");
oUl.onclick = function(ev){

    var ev = ev || window.event,
        target = ev.target || ev.srcElement;

    // 因为nodeName获取到的节点名是大写,因此需要使用toLowerCase()来将其转换为小写
    if (target.nodeName.toLowerCase() == "li") {
        //执行函数
    }
 }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
二、函数节流:

DOM操作是昂贵的,嗯,所以FB搞了个Virtual DOM来尽量简化你的DOM操作,《高性能javascript》一书也强调了要尽量减少DOM操作~嗯,为什么要提到这个呢?

相信很多人都用过两个事件:onresize跟onscroll,这两个事件一个在页面放大缩小时触发一个在页面滚动时触发,一般情况下伴随着这两个事件的肯定是DOM节点的定位啊,scrollTop距离的检测啊之类的操作,回到最初的问题,都说了DOM操作是很昂贵的你还在每一次onresize跟onscroll事件触发的时候去执行DOM操作,浪费机器性能很好玩么!

既然发现了问题,那么就要解决问题,解决的办法就是函数节流。函数节流背后的思想很简单:因为onresize跟onscroll这类事件是连续不断的触发的,那么在每次触发的时候我们就开一个定时器,将DOM操作延迟,然后在下一次事件触发的时候,我们把这个定时器给关掉,这样的结果就是onresize事件一路触发的时候,我们开定时器,关定时器,开定时器,关定时器。。。一直等到最后你不执行onresize的操作了,再等待延迟的时间后,最后这个没有被关闭的定时器开始执行回调函数,访问DOM节点。

对于函数节流,有两种实现的方法:

  • 第一种是《javascript高级程序设计》中的方法:
function throttle(method , context){
    clearTimeout(method.tId);

    method.tId=setTimeout(function(){
        method.call(context);
    },500);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 第二种是网上的方法:
function throttle(method , delay){
    var timer=null;

    return function(){
        var context = this, 
            args = arguments;
        clearTimeout(timer);

        timer = setTimeout(function(){
            method.apply(context , args);
        } , delay);
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
三、函数防抖:

函数节流是一个很好用的方案,但假如我并不希望每次都是要事件结束后等待delay的事件后才执行回调,但是又希望减少DOM操作,那该如何处理呢?

我们先给定一个时间段duration,过了这个时间段以后我们执行相应的操作,如果没有过这个时间段,那么就按照函数节流的思路,开关定时器就行了~

function throttle(method , duration ,delay ){
    var timer = null, 
        // 记录下开始执行函数的时间
        begin = new Date();

    return function(){
        var context = this, 
            args = arguments, 
            // 记录下当前时间
            current = new Date();
        // 函数节流里的思路
        clearTimeout(timer);

        // 记录下的两个时间相减再与duration进行比较
        if(current-begin >= duration){
             method.apply(context , args);
             begin = current;
        }else{  
            timer = setTimeout(function(){
                method.apply(context , args);
            } , delay);
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值