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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 前端学习笔记 – promise是什么?能解决什么问题?

    前端学习笔记 – promise是什么?能解决什么问题?返回目录promise是什么?promise是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态);Promise有以上三种状态,只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态状态一旦改变,就不会再变,任何时候都可以得

    2022年6月8日
    60
  • 高斯分布例题_高斯定理求半球面球心电场

    高斯分布例题_高斯定理求半球面球心电场给定心形曲线(x2+y2−1)3=x2y3(x^2+y^2-1)^3=x^2y^3,给定任意一点的坐标(X,Y)(X,Y)其中X~N(X,σx)X~N(X,\sigma_x),Y~N(Y,σy)Y~N(Y,\sigma_y)求点(X,Y)(X,Y)落入心形曲线内的概率。思路:以(X,Y)(X,Y)为中心,画出3∗σ3*\sigma半径的椭圆,求和心形曲线相交的体积。注意:心形曲线方程可化为x

    2022年10月16日
    0
  • Python安装whl文件之坑「建议收藏」

    Python安装whl文件之坑「建议收藏」有的时候,使用pipinstallxxx会失败,这个时候我们就需要下载xxx.whl文件,而xxx.whl在版本上有很多不兼容的地方需要注意 1.whl文件兼容性很差,同一文件分版本具体下载哪一个版本?可在pythonIDE中输入importpip;print(pip.pep425tags.get_supported())(pip10没有pep425tags()…

    2022年5月29日
    101
  • 函数strtol和strtok详解[通俗易懂]

    函数strtol和strtok详解[通俗易懂]一、strtol()函数的原型为:longintstrtol(constchar*nptr,char**endptr,intbase);函数的解释说明  这个函数会将参数nptr字符串根据参数base来转换成长整型数。参数base范围从2至36,或0。参数base代表采的进制方式,如base值为10则采用10进制,若base值为16则采用16进制等。当base值为0

    2022年7月14日
    16
  • pycharm 格式化代码[通俗易懂]

    pycharm 格式化代码[通俗易懂]有时候将空格键和tab键混用,在windows上没什么事情,但是如果移动到linux就会有问题,所以我们在移动到linux上之前要先格式化一下代码:ctrl+alt+L可以格式化,但是和锁屏快捷键冲突。 也可以,先选中代码,使用快捷键 ctrl+alt+i 。 …

    2022年8月25日
    3
  • 一步步学习Linux开发环境搭建与使用

    一步步学习Linux开发环境搭建与使用

    2022年1月22日
    42

发表回复

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

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