JavaScript中函数节流的理解

函数节流,就是对会频繁触发的函数事件做一些限制,让这些函数可以在每隔一定的时间或者每次满足一定的条件下再触发。一般我们会给他起一个名字throttle。也就是节流的意思。一般这样的函数有 resize事件、ontouchmove事件等。

举个简单的例子

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>测试函数节流</title>
    <style>
        #container{
            width: 100%;
            height: 200px;
            border: 1px solid #646464;
            color: #000000;
        }
    </style>

</head>
<body >
    <div id="container"></div>
</body>
<script>
    function alertSomething(){
        alert("您的鼠标正在移动");
    };
    document.getElementById("container").onmousemove = alertSomething;
</script>
</html>

上面的代码就是简单的渲染出来一个div 然后给这个div绑定了一个鼠标移动事件。但是我们在实际情况下,这种体验是不好的,因为这个事件会被十分频繁的触发。只要我们在这个div上移动鼠标就会弹出这个阻塞性的事件alert,所以我们希望每隔一定时间提醒一下“您的鼠标正在移动”。

下面写一个节流的函数throttle()

function throttle(func){
    var timer;
    return function(){
        var context = this;
        var args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function(){
            func.apply(context,args);
        },1000);
    }

}

然后我们修改一下页面上的代码:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>测试函数节流</title>
    <style>
        #container{
            width: 100%;
            height: 200px;
            border: 1px solid #646464;
            color: #000000;
        }
    </style>

</head>
<body >
    <div id="container"></div>
</body>
<script>

    function throttle(func){
        var timer;
        return function(){
            var context = this;
            var args = arguments;
            clearTimeout(timer);
            timer = setTimeout(function(){
                func.apply(context,args);
            },1000);
        }

    }
    function alertSomething(){
        alert("您的鼠标正在移动");
    };

    document.getElementById("container").onmousemove = throttle(alertSomething);
</script>
</html>

下面我仔细解释一下这个节流函数。

函数利用闭包的形式存储了一个timer定时器变量,说实话我刚开始看别人在写这个节流函数的时候,也是不太容易懂,只是马马虎虎觉得是这样的,直到我亲自实现一遍,才明白。在这里建议亲自动手,丰衣足食~~

这个timer变量当然也可以写在全局作用域中,但是可能会跟全局作用域中的变量产生冲突,所以在这里用闭包的形式来提供,防止它污染全局作用域。(看好多人在这里用“污染”,我想可能是如果有很多像timer这样的变量都放在全局作用域中,到时候肯定容易与在全局作用域中常用的变量混淆。因为它毕竟只是在这个节流函数做定时器使用)。

然后就是throttle函数返回的函数了。在这个函数中,要保存好传进来的执行上下文this,和参数arguments。应为我们要注意的是setTimeout()函数中作用域是全局的,也就是setTimeout中的this指的是window在这里这个执行上下文其实就是container对象,传入的参数就是鼠标移动这个事件的所有信息我们将这个container这个对象的鼠标移动事件函数重写成alertSomething这个函数。同时鼠标移动事件的信息也作为参数传入进去这一点也是我打印出他的相应信息后才恍然大悟,可能平时我们会理所当然的知道就是这样子的,但是为什么会这样子呢,我们知道了alertSomething这个函数的参数和执行上下文是怎么传进去的,那直接调用elementobj.onmousemove=function(){xxxxxx}这个函数的时候,上下文和参数是怎么传入进去的也应该是这样的原理吧。这是我的个人理解。

明白了上面的基本原理,我们再梳理一下节流函数发挥作用的过程:第一次调用这个节流函数的时候也就是第一次触发鼠标移动事件的时候,timer是没有的,所以clearTimeout(timer)清理的定时器也是没有的,但是等第二次触发鼠标移动事件的时候,鼠标移动事件的处理函数就是一直是throttle返回的函数了。首先会清理掉上次调用的时候的定时器,然后重新设置一个定时器。每次鼠标移动都是这样的处理过程,直到鼠标不再移动一秒钟后,定时器中的函数才被使用。

总之仔仔细细理解一下这个过程感觉真好!

但是如果我们需要自定义延迟的事件的话怎么办呢,下面我们可以再改进一下:

function throttle(func,delay){
    var timer;
    return function(){
        var context = this;
        var args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function(){
            func.apply(context,args);
        },delay);
    }

}
function dealMouseMove(){
    alert("您的鼠标正在移动");
};
document.getElementById("container").onmousemove = throttle(dealMouseMove,500);

在这里感谢 Toobugflowmemo 两位大神的指正,上面函数的功能更应该称之为debounce,也即是消除抖动的意思。前端技术中常用的throttle相当于一个频率控制器,让一段时间内快速触发很多次的事件处理程序可以只触发一定的次数, 而debounce不只是减少了触发次数,而且要满足一定的条件下才会触发。两者实现方法都是差不多的,叫法不一样而已,不能将两者割裂开来。

可以参考的相关网址:http://www.css88.com/archives/4648 可以参考的相关网址:http://www.alloyteam.com/2012/11/javascript-throttle/

本文转载自https://segmentfault.com/a/1190000004663513