码云笔记前端博客
Home > 每日·壹题 > 第7题:节流函数的作用是什么?应用场景有哪些 如何实现

第7题:节流函数的作用是什么?应用场景有哪些 如何实现

2019-06-18 分类:每日·壹题 作者:码云 阅读(2171)

本文共计3293个字,预计阅读时长需要9分钟。

节流概念

节流跟防抖,它们既有相似之处但又有所不同,很容易混淆。这里通过两个比喻来加深理解,先来说说节流。

节流的概念可以想象一下水坝,你建了水坝在河道中,不能让水流动不了,你只能让水流慢些。换言之,你不能让用户的方法都不执行。(个人比较喜欢这个比喻,因为它很形象的说出了跟防抖的区别。)

节流函数的作用

节流函数的作用是规定一个单位时间,在这个单位时间内最多只能触发一次函数执行,如果这个单位时间内多次触发函数,只能有一次生效。

举例说明:小明的妈妈和小明约定好,如果小明在周考中取得满分,那么当月可以带他去游乐场玩,但是一个月最多只能去一次。

这其实就是一个节流的例子,在一个月的时间内,去游乐场最多只能触发一次。即使这个时间周期内,小明取得多次满分。

节流应用场景

1.按钮点击事件

2.拖拽事件

3.onScoll

4.计算鼠标移动的距离(mousemove)

节流函数实现

利用时间戳实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<span class="token keyword">function</span> <span class="token function">throttle</span> <span class="token punctuation">(</span>func<span class="token punctuation">,</span> delay<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">var</span> lastTime <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
    <span class="token keyword">function</span> <span class="token function">throttled</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">var</span> context <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> args <span class="token operator">=</span> arguments<span class="token punctuation">;</span>
        <span class="token keyword">var</span> nowTime <span class="token operator">=</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span><span class="token punctuation">(</span>nowTime <span class="token operator">&gt;</span> lastTime <span class="token operator">+</span> delay<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            func<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
            lastTime <span class="token operator">=</span> nowTime<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token comment">//防抖函数最终返回的是一个函数</span>
    <span class="token keyword">return</span> throttled<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

利用定时器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<span class="token keyword">function</span> <span class="token function">throttle</span><span class="token punctuation">(</span>func<span class="token punctuation">,</span> delay<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">var</span> timeout <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
    <span class="token keyword">function</span> <span class="token function">throttled</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">var</span> context <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">;</span>
        <span class="token keyword">var</span> args <span class="token operator">=</span> arguments<span class="token punctuation">;</span>
        <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>timeout<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            timeout <span class="token operator">=</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>
                func<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token function">clearTimeout</span><span class="token punctuation">(</span>timeout<span class="token punctuation">)</span><span class="token punctuation">;</span>
                timeout<span class="token operator">=</span><span class="token keyword">null</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span> delay<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> throttled<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

时间戳和定时器的方式都没有考虑最后一次执行的问题,比如有个按钮点击事件,设置的间隔时间是1S,在第0.5S,1.8S,2.2S点击,那么只有0.5S和1.8S的两次点击能够触发函数执行,而最后一次的2.2S会被忽略。

组合实现,允许设置第一次或者最后一次是否触发函数执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function throttle (func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function () {
        previous = options.leading === false ? 0 : Date.now() || new Date().getTime();
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
    };

    var throttled = function () {
        var now = Date.now() || new Date().getTime();
        if (!previous &amp;&amp; options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining &lt;= 0 || remaining &gt; wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            result = func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout &amp;&amp; options.trailing !== false) {
            // 判断是否设置了定时器和 trailing
            timeout = setTimeout(later, remaining);
        }
        return result;
    };

    throttled.cancel = function () {
        clearTimeout(timeout);
        previous = 0;
        timeout = context = args = null;
    };

    return throttled;
}

使用很简单:

1
btn.onclick = throttle(handle, 1000, {leading:true, trailing: true});

以判断页面是否滚动到底部为例,普通的做法就是监听 window 对象的 scroll 事件,然后再函数体中写入判断是否滚动到底部的逻辑:

1
2
3
4
5
6
7
8
9
10
<span class="pl-en">$</span>(<span class="pl-c1">window</span>).<span class="pl-en">on</span>(<span class="pl-s"><span class="pl-pds">'</span>scroll<span class="pl-pds">'</span></span>, <span class="pl-k">function</span>() {
  <span class="pl-c">// 判断是否滚动到底部的逻辑</span>
  <span class="pl-k">let</span> pageHeight <span class="pl-k">=</span> <span class="pl-en">$</span>(<span class="pl-s"><span class="pl-pds">'</span>body<span class="pl-pds">'</span></span>).<span class="pl-c1">height</span>(),
    scrollTop <span class="pl-k">=</span> <span class="pl-en">$</span>(<span class="pl-c1">window</span>).<span class="pl-en">scrollTop</span>(),
    winHeight <span class="pl-k">=</span> <span class="pl-en">$</span>(<span class="pl-c1">window</span>).<span class="pl-c1">height</span>(),
    thresold <span class="pl-k">=</span> pageHeight <span class="pl-k">-</span> scrollTop <span class="pl-k">-</span> winHeight;
  <span class="pl-k">if</span> (thresold <span class="pl-k">&gt;</span> <span class="pl-k">-</span><span class="pl-c1">100</span> <span class="pl-k">&amp;&amp;</span> thresold <span class="pl-k">&lt;=</span> <span class="pl-c1">20</span>) {
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>end<span class="pl-pds">'</span></span>);
  }
});

如下图:

 节流(throttle)函数的作用是什么

这样做的一个缺点就是比较消耗性能,因为当在滚动的时候,浏览器会无时不刻地在计算判断是否滚动到底部的逻辑,而在实际的场景中是不需要这么做的,在实际场景中可能是这样的:在滚动过程中,每隔一段时间在去计算这个判断逻辑。而函数节流所做的工作就是每隔一段时间去执行一次原本需要无时不刻地在执行的函数,所以在滚动事件中引入函数的节流是一个非常好的实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<span class="pl-c">// 添加节流函数</span>
<span class="pl-k">function</span> <span class="pl-en">throttle</span>(<span class="pl-smi">fn</span>, <span class="pl-smi">interval</span> <span class="pl-k">=</span> <span class="pl-c1">300</span>) {
  <span class="pl-k">let</span> canRun <span class="pl-k">=</span> <span class="pl-c1">true</span>;
  <span class="pl-k">return</span> <span class="pl-k">function</span>() {
    <span class="pl-k">if</span> (<span class="pl-k">!</span>canRun) <span class="pl-k">return</span>;
    canRun <span class="pl-k">=</span> <span class="pl-c1">false</span>;
    <span class="pl-c1">setTimeout</span>(() <span class="pl-k">=&gt;</span> {
      <span class="pl-smi">fn</span>.<span class="pl-c1">apply</span>(<span class="pl-c1">this</span>, <span class="pl-c1">arguments</span>);
      canRun <span class="pl-k">=</span> <span class="pl-c1">true</span>;
    }, interval);
  };
}
<span class="pl-en">$</span>(<span class="pl-c1">window</span>).<span class="pl-en">on</span>(
  <span class="pl-s"><span class="pl-pds">'</span>scroll<span class="pl-pds">'</span></span>,
  <span class="pl-en">throttle</span>(<span class="pl-k">function</span>() {
    <span class="pl-c">// 判断是否滚动到底部的逻辑</span>
    <span class="pl-k">let</span> pageHeight <span class="pl-k">=</span> <span class="pl-en">$</span>(<span class="pl-s"><span class="pl-pds">'</span>body<span class="pl-pds">'</span></span>).<span class="pl-c1">height</span>(),
      scrollTop <span class="pl-k">=</span> <span class="pl-en">$</span>(<span class="pl-c1">window</span>).<span class="pl-en">scrollTop</span>(),
      winHeight <span class="pl-k">=</span> <span class="pl-en">$</span>(<span class="pl-c1">window</span>).<span class="pl-c1">height</span>(),
      thresold <span class="pl-k">=</span> pageHeight <span class="pl-k">-</span> scrollTop <span class="pl-k">-</span> winHeight;
    <span class="pl-k">if</span> (thresold <span class="pl-k">&gt;</span> <span class="pl-k">-</span><span class="pl-c1">100</span> <span class="pl-k">&amp;&amp;</span> thresold <span class="pl-k">&lt;=</span> <span class="pl-c1">20</span>) {
      <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>end<span class="pl-pds">'</span></span>);
    }
  })
);

如图:

函数节流

加上函数节流之后,当页面再滚动的时候,每隔300ms才会去执行一次判断逻辑。

简单来说,函数的节流就是通过闭包保存一个标记(canRun=true),在函数的开头判断这个标记是否为true,如果为true的话就继续执行函数,否则则return掉,判断完标记后立即把这个标记设为false,然后把外部传入的函数的执行包在一个setTimeout中,最后在setTimeout执行完毕后再把标记设置为true(这里很关键),表示可以执行下一次的循环了。当setTimeout还未执行的时候,canRun这个标记始终为false,在开头的判断中被return掉。

防抖和节流都是为了避免事件的频繁触发,有些事件无论是防抖还是节流都做到性能上的优化,要根据业务需求选择合适的方案。

具体参考我之前写的文章:《第6题:什么是防抖和节流 有什么区别 如何实现

结语

我的回答如有不对的地方,麻烦务必指出来,我及时改正,以免误导别人,让我们共同进步吧!

「除特别注明外,本站所有文章均为码云笔记原创,转载请保留出处!」

赞(1) 打赏

觉得文章有用就打赏一下文章作者

支付宝
微信
1

觉得文章有用就打赏一下文章作者

支付宝
微信

上一篇:

下一篇:

你可能感兴趣

共有 0 条评论 - 第7题:节流函数的作用是什么?应用场景有哪些 如何实现

博客简介

码云笔记 mybj123.com,一个专注Web前端开发技术的博客,主要记录和总结博主在前端开发工作中常用的实战技能及前端资源分享,分享各种科普知识和实用优秀的代码,以及分享些热门的互联网资讯和福利!码云笔记有你更精彩!
更多博客详情请看关于博客

精彩评论

站点统计

  • 文章总数: 458 篇
  • 分类数目: 13 个
  • 独立页面: 8 个
  • 评论总数: 215 条
  • 链接总数: 14 个
  • 标签总数: 1011 个
  • 建站时间: 495 天
  • 访问总量: 8647995 次
  • 最近更新: 2019年10月21日