前言

js的典型的场景

  • 监听页面的scroll事件
  • 拖拽事件
  • 监听鼠标的 mousemove 事件

    ...

这些事件会频繁触发会影响性能,如果使用节流,降低频次,保留了用户体验,又提升了执行速度,节省资源。

原理

节流的原理:持续触发某事件,每隔一段时间,只执行一次。

通俗点说,3 秒内多次调用函数,但是在 3 秒间隔内只执行一次,第一次执行后 3 秒 无视后面所有的函数调用请求,也不会延长时间间隔。3 秒间隔结束后则开始执行新的函数调用请求,然后在这新的 3 秒内依旧无视后面所有的函数调用请求,以此类推。

简单来说:每隔单位时间( 3 秒),只执行一次。

实现方式

目前比较主流的实现方式有两种:时间戳、定时器。

时间戳实现

使用时间戳实现:首先初始化执行事件的时间previous为0,然后将当前的时间戳减去上次执行时间(now - previous),如果大于wait,则直接执行函数,并且将此时的执行时间now赋给previous(previous = now)。

由于首次previous = 0,则此时函数第一次触发就会立即执行。

后续则每隔wait时间执行一次,如果停止触发,则不会再执行函数。

// 由于一开始now - 0 > wait,则这个写法,时间会立即执行,没过一秒会执行一次,停止触发,则不会再执行事件
function throttle(func, wait = 500) {
let context, now;
let previous = 0; // 设置过去的执行时间初始值为0
return function (...args) {
context = this;
now = +(Date.now() || new Date().getTime());
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
};
}

定时器实现

使用定时器实现:首先初始化timeout,然后定义!timeout为true的情况下,直接执行setTimeout,,等待wait时间后执行函数,然后清空timeout,以此类推,重新进入也会按上述执行。

由于进入函数,就执行setTimeout,所以不会立即触发函数执行。

后续则每隔wait时间执行一次,如果停止触发,而后还会触发执行一次函数。

// 由于一进入就创建了定时器,所以不会立即触发函数执行
function throttle(func, wait = 500) {
let context, timeout; return function (...args) {
context = this; if (!timeout) {
timeout = setTimeout(function () {
timeout = null;
func.apply(context, args);
}, wait);
}
};
}

合并版本

如果,我们需要既刚开始就立即执行,停止触发后,还会触发执行一次函数。

下面,我们将定时器和时间戳合并,组成一个全新的节流版本。

function throttle(func, wait = 500) {
let context, timeout, result;
let previous = 0;
const throttled = function (...args) {
context = this;
const now = +(Date.now() || new Date().getTime()); // 当前时间
// 下次触发 func 剩余时间
const remaining = wait - (now - previous); // 如果没有剩余时间或者改了系统时间,这时候不需要等待,直接立即执行,这样就会第一次就执行
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout) {
// 剩余的情况就是remaining<=wait的情况,这里使用setTimeout就可以最后也会执行一次
timeout = setTimeout(function () {
timeout = null;
previous = +(Date.now() || new Date().getTime()); // 这里是将previous重新赋值当前时间
func.apply(context, args);
}, remaining);
}
};
return throttled;
}

合并版本优化

由于合并后的版本并没用返回值的优化+取消功能。

下面对代码进行返回值+取消功能优化:

function throttle(func, wait = 500) {
let context, timeout, result;
let previous = 0; const showResult = function (e1, e2) {
result = func.apply(e1, e2);
return result;
}; const throttled = function (...args) {
context = this;
const now = +(Date.now() || new Date().getTime()); // 当前时间
// 下次触发 func 剩余时间
const remaining = wait - (now - previous); // 如果没有剩余时间或者改了系统时间,这时候不需要等待,直接立即执行,这样就会第一次就执行
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
return showResult(context, args);
} else if (!timeout) {
// 剩余的情况就是remaining<=wait的情况,这里使用setTimeout就可以最后也会执行一次
timeout = setTimeout(function () {
timeout = null;
previous = +(Date.now() || new Date().getTime()); // 这里是将previous重新赋值当前时间
return showResult(context, args);
}, remaining);
}
retrun result
}; throttled.cancel = function () {
if (timeout !== undefined) {
clearTimeout(timeout);
}
previous = 0;
context = timeout = result = undefined;
};
return throttled;
}

功能性优化

有时候,我们也希望无头有尾,或者有头无尾。

function throttle(func, wait = 500, options = {}) {
let context, timeout, result;
let previous = 0; // 如果同时设置无头无尾,则直接使用默认设置,其他情况,则走下述操作
if (!(options.leading === false && options.trailing === false)) {
leading = !!options.leading; // 默认去除立即执行部分
trailing = "trailing" in options ? !!options.trailing : true; // 默认保留尾部
} // 返回原函数的return
const showResult = function (e1, e2) {
result = func.apply(e1, e2);
return result;
}; // 获取当前时间
const getNow = function () {
return +(Date.now() || new Date().getTime());
}; const throttled = function (...args) {
context = this;
const now = getNow(); // 当前时间
// 下次触发 func 剩余时间
if (!previous && leading === false) previous = now;
const remaining = wait - (now - previous); // 如果没有剩余时间或者改了系统时间,这时候不需要等待,直接立即执行,这样就会第一次就执行
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
return showResult(context, args);
} else if (!timeout && trailing !== false) {
// 剩余的情况就是remaining<=wait的情况,这里使用setTimeout就可以最后也会执行一次
timeout = setTimeout(function () {
timeout = null;
previous = options.leading === false ? 0 : getNow(); // 这里是将previous重新赋值当前时间
return showResult(context, args);
}, remaining);
}
return result;
}; throttled.cancel = function () {
if (timeout !== undefined) {
clearTimeout(timeout);
}
previous = 0;
context = timeout = result = undefined;
};
return throttled;
}

这里,如果options不传参数,函数默认设置

let leading = false
let trailing = true

也就是无头有尾。

如果同时设置无头无尾,则会直接采用默认设置,无头有尾。

// 如果同时设置无头无尾,则直接使用默认设置,其他情况,则走下述操作
if (!(options.leading === false && options.trailing === false)) {
leading = !!options.leading; // 默认去除立即执行部分
trailing = "trailing" in options ? !!options.trailing : true; // 默认保留尾部
}

演示地址

可以去Github仓库查看演示代码

跟着大佬学系列

主要是日常对每个进阶知识点的摸透,跟着大佬一起去深入了解JavaScript的语言艺术。

后续会一直更新,希望各位看官不要吝啬手中的赞。

感谢各位的支持!!!

如果有错误或者不严谨的地方,请务必给予指正,十分感谢!!!

喜欢或者有所启发,欢迎 star!!!

参考

原文地址

【跟着大佬学JavaScript】之节流

【跟着大佬学JavaScript】之节流的更多相关文章

  1. 【跟着大佬学JavaScript】之lodash防抖节流合并

    前言 前面已经对防抖和节流有了介绍,这篇主要看lodash是如何将防抖和节流合并成一个函数的. 初衷是深入lodash,学习它内部的好代码并应用,同时也加深节流防抖的理解.这里会先从防抖开始一步步往后 ...

  2. 【跟着大佬学JavaScript】之数组去重(结果对比)

    前言 数组去重在面试和工作中都是比较容易见到的问题. 这篇文章主要是来测试多个方法,对下面这个数组的去重结果进行分析讨论.如果有不对的地方,还请大家指出. const arr = [ 1, 1, &q ...

  3. 怎么学JavaScript?

    作者:小不了链接:https://zhuanlan.zhihu.com/p/23265155来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 鉴于时不时,有同学私信问我( ...

  4. 浅谈javascript函数节流

    浅谈javascript函数节流 什么是函数节流? 函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等 ...

  5. 简述JavaScript函数节流

    为什么要用函数节流 浏览器中某些计算和处理要比其他的昂贵很多.例如,DOM 操作比起非 DOM 交互需要更多的内存和 CPU 时间.连续尝试进行过多的 DOM 相关操作可能会导致浏览器挂起,有时候甚至 ...

  6. 统一回复《怎么学JavaScript?》

    作者:小不了链接:https://zhuanlan.zhihu.com/p/23265155来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 鉴于时不时,有同学私信问我( ...

  7. 从头开始学JavaScript (十一)——Object类型

    原文:从头开始学JavaScript (十一)--Object类型 一.object类型 一个object就是一系列属性的集合,一个属性包含一个名字(属性名)和一个值(属性值). object对于在应 ...

  8. 从头开始学JavaScript (十二)——Array类型

    原文:从头开始学JavaScript (十二)--Array类型 一.数组的创建 注:ECMAscript数组的每一项都可以保存任何类型的数据 1.1Array构造函数 var colors = ne ...

  9. 从头开始学JavaScript (十)——垃圾收集

    原文:从头开始学JavaScript (十)--垃圾收集 一.垃圾收集 1.1javascript垃圾收集机制: 自动垃圾收集,执行环境会负责管理代码执行过程中的使用的内存.而在C和C++之类的语言中 ...

随机推荐

  1. MySQL远程连接、用户授权

    目录 MySQL远程连接 创建用户.授权 MySQL添加用户.删除用户.授权及撤销权限 MySQL可授予用户的执行权限 MySQL远程连接 远程连接 授权 常见权限表 相关库:mysql 相关表:us ...

  2. 我向PostgreSQL社区贡献的功能:空闲会话超时

    经过约八个月的努力,终于完成了 PostgreSQL 空闲会话超时断开的功能. 该功能将在版本 14 中发布. 这是我第一次向 PostgreSQL 提供功能,虽然之前也有向社区提供过补丁,但是这次整 ...

  3. vue下一代状态管理Pinia.js 保证你看的明明白白!

    1.pinia的简单介绍 Pinia最初是在2019年11月左右重新设计使用Composition API的 Vue 商店外观的实验. 从那时起,最初的原则相同,但 Pinia 适用于 Vue 2 和 ...

  4. 攻防世界-MISC:hit-the-core

    这是攻防世界MISC高手进阶区的题目,题目如下: 点击下载附件一,解压后得到一个后缀为.core的文件,用string分离一下,得到如下结果: 通过观察发现,每隔四个小写字母就可以看到一个大写字母,刚 ...

  5. python连接redis、redis字符串操作、hash操作、列表操作、其他通用操作、管道、django中使用redis

    今日内容概要 python连接redis redis字符串操作 redis之hash操作 redis之列表操作 redis其他 通用操作,管道 django中使用redis 内容详细 1.python ...

  6. [AcWing 768] 忽略大小写比较字符串大小

    点击查看代码 #include<iostream> using namespace std; string a, b; int main() { getline(cin, a); getl ...

  7. 【ACM程序设计】求最小生成树 Kuskual算法

    Kuskual算法 流程 1 将图G看做一个森林,每个顶点为一棵独立的树 2 将所有的边加入集合S,即一开始S = E( 并查集) 3 从S中拿出一条最短的边(u,v),如果(u,v)不在同一棵树内, ...

  8. Docker系列教程03-Docker私有仓库搭建(registry)

    简介 仓库(Repository)是集中存放镜像的地方,又分为公共镜像和私有仓库. 当我们执行docker pull xxx的时候,它实际上是从registry.docker.com这个地址去查找,这 ...

  9. C++进阶-1-模板基础(函数模板、类模板)

    C++进阶 模板 1.1 函数模板 1 #include<iostream> 2 using namespace std; 3 4 // 模板 5 6 // 模板的简单实例 7 // 要求 ...

  10. .NET ORM 仓储层必备的功能介绍之 FreeSql Repository 实现篇

    写在开头 2018年11月的某一天,头脑发热开启了 FreeSql 开源项目之旅,时间一晃已经四年多,当初从舒服区走向一个巨大的坑,回头一看后背一凉.四年时间从无到有,经历了数不清的日夜奋战(有人问我 ...