debounce实现 js_前端面试题——自己实现debounce

debounce实现 js_前端面试题——自己实现debounce前端面试,总会被问到这类问题:你知道debounce是什么么?你知道debounce什么时候用么?来来来,能给我实现一个debounce么?了解debounce以及实现方法,不仅会帮助我们面试,也是对我们技术的一次提升。废话不说,来不及了,我们一起学习debounce。什么是debounce?什么时候使用debounce?翻看Underscore的文档,它是这么描述debounce的:返回fun…

大家好,又见面了,我是你们的朋友全栈君。

前端面试,总会被问到这类问题:你知道debounce是什么么?

你知道debounce什么时候用么?

来来来,能给我实现一个debounce么?debounce实现 js_前端面试题——自己实现debounce

了解debounce以及实现方法,不仅会帮助我们面试,也是对我们技术的一次提升。废话不说,来不及了,我们一起学习debounce。

什么是debounce?什么时候使用debounce?

翻看Underscore的文档,它是这么描述debounce的:返回 function 函数的防反跳版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后. 对于必须在一些输入(多是一些用户操作)停止到达之后执行的行为有帮助。 例如: 渲染一个Markdown格式的评论预览, 当窗口停止改变大小之后重新计算布局, 等等.

这段话是什么意思呢?我们看一个例子:

HTML结构:

请滚动页面

滚动数目

0

CSS样式:

h1 {

height: 2000px;

}

.countWrapper {

position: fixed;

top: 100px;

left: 100px;

}

对应的JS代码:

var count = 0;

var updateCount = function(ev) {

console.log(ev)

count++;

document.getElementById(“count”).innerHTML = count;

}

window.onscroll = updateCount;

可以看到,随着鼠标滚动,updateCount这个事件不停地被触发。我们的例子当中,updateCount所做的事情比较简单,函数执行也比较快,但是,如果在复杂的系统当中,如Underscore文档中提到的,渲染一个Markdown格式的评论预览,如果我们每次都在window.onresize改变的时候重新计算布局,那么由于单位时间内,我们可能触发几十次resize事件,那么我们就要重新计算几十次布局,这给了系统很大的 压力,可能造成卡顿,而且,很明显,我们最关心的是窗口停止resize时候的评论预览,中间那么多次渲染是没有必要的。

怎么解决这个问题呢?

那就是,是延迟updateCount的执行,即只有在onscroll这个函数停止调用wait毫秒时间之后,再去执行updateCount。

基本实现

debounce本质上,是一个定时器setTimeout,在wait毫秒时间之后,执行传入的函数:

function debounce(func, wait, immediate) {

var timeout;

var debounced = function() {

if (timeout) clearTimeout(timeout);

timeout = setTimeout(func, wait);

}

return debounced;

}

调用方法:

window.onscroll = debounce(updateCount, 1000);

window.onscroll在每次滚动的时候,都会被调用debounce(updateCount, 1000),在debounce函数内部,如果之前已经存在定时器,那么就清除已有的定时器,重新开始计时。这样,如果滚动过于频繁地被触发,则之前滚动所开启的定时器都会被紧接而来的下一个滚动事件清除,只有最后一个滚动事件触发的定时器才会被保存,最终在1000ms之后执行updateCount函数。

完善程序

看过我之前文章的朋友们,应该会想到,一般模拟某个函数的时候,都需要处理一些事情:比如函数内部this的指向、参数的传递、原型链是否正确、是否能够正确处理返回值,等等。

我们先看this的指向。

如果直接调用window.onscroll = updateCount;的时候,updateCount内部的this是隐式绑定(如果不清楚什么是隐式绑定,请阅读我的《前端面试题——十分钟搞懂this》),那么this指向的是调用onscroll的元素,我的例子中是window,当然,你也可以在其他元素上调用onscroll,如:target.onscroll,那么这时的this就指向target。但是我们的模拟debounce当中,updateCount是在1000ms之后调用的,这个时候的调用环境是?恩?global?

怎么办呢?

我们把debounce调用时候的this保存给context,然后使用apply调用内部的func,并指定context为func的this:

function debounce(func, wait, immediate) {

var timeout;

var debounced = function() {

var context = this;

if (timeout) clearTimeout(timeout);

timeout = setTimeout(function() {

func.apply(context);

}, wait);

}

return debounced;

}

然后再看看参数的传递。

这个就很容易了,把参数保存为args,然后传入apply作为第二个参数。直接看代码:

function debounce(func, wait, immediate) {

var timeout;

var debounced = function() {

var context = this;

var args = arguments;

if (timeout) clearTimeout(timeout);

timeout = setTimeout(function() {

func.apply(context, args);

}, wait);

}

return debounced;

}

最后处理返回值。

function debounce(func, wait, immediate) {

var timeout, result;

var debounced = function() {

var context = this;

var args = arguments;

if (timeout) clearTimeout(timeout);

timeout = setTimeout(function() {

result = func.apply(context, args);

}, wait);

return result;

}

return debounced;

}

由于result是在setTimeout内部更新的,所以,其实return result会返回undefined,因此这个result并不是func函数执行之后真正的返回值。不过result接下来会用到,所以我们先放在那里吧。

立即执行

到这里我们的debounce就写完了么?对,已经写完了。不过underscore中的debounce还有第三个参数:immediate。这个参数是做什么用的呢?传参 immediate 为 true, debounce会在 wait 时间间隔的开始调用这个函数 。(注:并且在 wait 的时间之内,不会再次调用。)在类似不小心点了提交按钮两下而提交了两次的情况下很有用。

把true传递给immediate参数,会让debounce在wait时间开始计算之前就触发函数(也就是没有任何延时就触发函数),而不是过了wait时间才触发函数,而且在wait时间内也不会触发(相当于把func的执行锁住)。 如果不小心点了两次提交按钮,第二次提交就会不会执行。

那我们根据immediate的值来决定如何执行func。如果是immediate的情况下,我们立即执行func,并在wait时间内锁住func的执行,wait时间之后再触发,才会重新执行func,以此类推。

function debounce(func, wait, immediate) {

var timeout, result;

var debounced = function() {

var context = this;

var args = arguments;

if (timeout) clearTimeout(timeout);

if (immediate) {

var callNow = !timeout;

timeout = setTimeout(function(){

timeout = null;

}, wait);

if (callNow) result = func.apply(this, args);

} else {

timeout = setTimeout(function() {

result = func.apply(context, args);

}, wait);

}

return result;

}

return debounced;

}

如果使用window.onscroll = debounce(updateCount, 1000, true);调用函数,那么会进入if (immediate) 这种情况。我们分为首次调用,调用后wait结束之前再次调用和调用后wait结束之后再次调用三种情况讨论。

首次调用:如果是第一次调用的话,timeout是undefined,那么 callNow就是true。而timeout会被更新为定时器返回的ID,然后调用result = func.apply(this, args);。另外,result值在这里能被正确更新,并正确返回。

调用后wait结束之前再次调用:这个时候,timeout还是等于定时器ID(clearTimeout并不会删掉timeout中保存的ID),那么callNow就是false,就不会执行func.apply(this, args);

调用后wait结束之后再次调用:由于定时器的wait时间已过,timeout被更新为null,那么callNow就是true,又可以执行func.apply(this, args);了,同时锁住timeout,以此类推。

程序可以简单改一下:

function debounce(func, wait, immediate) {

var timeout, result;

var debounced = function() {

var context = this;

var args = arguments;

if (timeout) clearTimeout(timeout);

var later = function() {

timeout = null;

if (!immediate) result = func.apply(context, args);

};

if (immediate) {

var callNow = !timeout;

timeout = setTimeout(later, wait);

if (callNow) result = func.apply(this, args);

} else {

timeout = setTimeout(later, wait);

}

return result;

}

return debounced;

}

使用later来保存func函数的调用情况。这么改的原因么?可能是为了和underscore本身的debounce长得像一点吧。

其实,上面的代码还可以优化一下,让代码更简洁:

function debounce(func, wait, immediate) {

var timeout, result;

var debounced = function() {

var context = this;

var args = arguments;

if (timeout) clearTimeout(timeout);

var later = function() {

timeout = null;

if (!immediate) result = func.apply(context, args);

};

var callNow = immediate && !timeout;

timeout = setTimeout(later, wait);

if (callNow) result = func.apply(this, args);

return result;

}

return debounced;

}

取消debounce

Underscore中的debounce还有一个功能:如果需要取消预定的 debounce ,可以在 debounce 函数上调用 .cancel()。

这个就把setTimeout事件清除掉,并把timeout直接更新为null就可以:

debounced.cancel = function() {

clearTimeout(timeout);

timeout = null;

};

debounce我们就写完了,完整代码如下所示:

function debounce(func, wait, immediate) {

var timeout, result;

var debounced = function() {

var context = this;

var args = arguments;

if (timeout) clearTimeout(timeout);

var later = function() {

timeout = null;

if (!immediate) result = func.apply(context, args);

};

var callNow = immediate && !timeout;

timeout = setTimeout(later, wait);

if (callNow) result = func.apply(this, args);

return result;

}

debounced.cancel = function() {

clearTimeout(timeout);

timeout = null;

};

return debounced;

}

希望看这篇文章,你不仅知道什么时候使用debounce,也可以写出自己的debounce,顺顺利利通过面试。祝大家前端学习一切顺利!

关注我的公众号:前端三剑客。分享前端面试与算法面试的分析与总结!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/151567.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • jmeter安装及使用基本教程「建议收藏」

    jmeter安装及使用基本教程「建议收藏」一、安装1.安装jdk,配置环境变量附:JRE(JavaRuntimeEnvironment)Java运行环境,用来运行JAVA程序的。JDK(JavaDevelopmentKit)Java开发工具包,包含JRE。因此只需要下载安装JDK即可中。JDK是SunMicrosystems针对Java开发员的产品,JSP运行环境需要JDK的支持。JDK是整个Java的核心,…

    2022年5月17日
    37
  • go语言的type func()用法

    go语言的type func()用法在 go 语言中 type 可以定义任何自定义的类型比如熟悉的 typedogstruc typemyIntint 等等所以 func 也是可以作为类型自定义的 typemyFuncfu int int 意思是自定义了一个叫 myFunc 的函数类型 这个函数的签名必须符合输入为 int 输出为 int 已知 相同底层类型的变量之间是可以相互转换的 例如从一个取值范围小的 int16 转为取值范围大的 int32 所以 自定义的 myInt 和 int 之间也是可以转换的 typemyIn

    2025年6月8日
    2
  • java 大整数取余_java 整数取余

    java 大整数取余_java 整数取余java整数取余是建立在java整数除法的基础上的,java整数除法可以参考我的上一篇文章java整数除法。Theremainderoperationforoperandsthatareintegersafterbinarynumericpromotion(§5.6.2)producesaresultvaluesuchthat(a/b)*b+(a%b)is…

    2022年5月18日
    37
  • 虚拟机安装gcc「建议收藏」

    虚拟机安装gcc「建议收藏」(确保虚拟机网络通畅)1在虚拟机桌面上右击【打开终端】2输入sudoapt-getupdate回车输入开机密码(输入时密码并不显示,输完后回车)等待出现下一步指示3输入sudoapt-getinstallgcc回车输入密码等待(我等了约30min)出现下一步指示4输入gcc–version回车完…

    2022年5月10日
    49
  • ubuntu 更新源详细操作步骤「建议收藏」

    ubuntu 更新源详细操作步骤「建议收藏」由于linux系统自带的镜像源都在国外,国内用户下载或更新软件会比较慢,有时是非常慢,所以国内某些机构,如大学,研究院所,就在国内建了linux的镜像源服务器共国内linux用户使用,而我们要使用这些源,就要更改自己linux系统的更新源配置文件,接下来详述更新源操作步骤。1.首先我们要找到国内的镜像源路径我选择了清华的镜像源,链接如下:https://mirrors.tuna.t

    2022年5月14日
    47
  • 图解正向代理和反向代理的区别_nginx配置多个正向代理

    图解正向代理和反向代理的区别_nginx配置多个正向代理套用古龙武侠小说套路来说,代理服务技术是一门很古老的技术,是在互联网早期出现就使用的技术。一般实现代理技术的方式就是在服务器上安装代理服务软件,让其成为一个代理服务器,从而实现代理技术。常用的代理技术分为正向代理、反向代理和透明代理。本文就是针对这三种代理来讲解一些基本原理和具体的适用范围,便于大家更深入理解代理服务技术。一、正向代理(ForwardProxy)&nbs…

    2022年8月30日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号