这个系列也没啥花头,就是来整平时面试的一些手写函数,考这些简单实现的好处是能看出基本编码水平,且占用时间不长,更全面地看出你的代码实力如何。一般不会出有很多边界条件的问题,那样面试时间不够用,考察不全面。
平时被考到的 api 如果不知道或不清楚,直接问面试官就行, api 怎么用这些 Google 下谁都能马上了解的知识也看不出水平。关键是在实现过程,和你的编码状态、习惯、思路清晰程度等。
注意是简单实现,不是完整实现,重要的是概念清晰和实现思路清晰,建议
先写伪代码
,再实现具体功能。
核心概念防抖节流 我这篇已经很详细了 我不赘述,想更了解细节请仔细看这篇。
4. 函数防抖 (debounce)
是什么
简单来说就是把多次执行组合成一次执行。
比如我们设置了一个时间间隔 5 秒,当事件触发的间隔超过 5 秒
,(回调)函数才会执行
,如果在 5 秒内,事件又被触发,则刷新这个 5 秒,至少5秒后事件没被触发
才执行函数。
简单手写实现
实现
- 第 0 步,写个测试用例先,有时候你可以要求面试官给出个,来保证理解的准确性,如果他不给,你写完用例也一定要确认下是否满足要求,不要实现原本就沟通不到位的方案,这样也展现了你的沟通能力。
// 用户高频率执行的函数(需要防抖的函数),也许是个异步请求列表,成本比较高不需要频繁调用
function userHighRequencyAction(e, content) {
console.log(e, content);
}
// 给这个高频的方法,加防抖方案输出一个防抖的function
var userDebounceAction = debounce(userHighRequencyAction, 1000);
// 如何触发那个高频函数 绑定一个onmousemove事件,来模拟高频触发
document.onmousemove = function (e) {
userDebounceAction(e, 'test debounce'); // 给防抖函数传个参
}
- 那我们就先写出伪代码,理清思路
基本思路就是利用 setTimeout
这个WebApi的延迟效果,设置一个定时器,当没有到达延迟时间就清除定时器
(clearTimeout),并建立一个新的定时器
,继续等延迟时间,如此循环。
// 测试用例得
function debounce(func, wait) {
// 声明一个定时器
// 我们需要返回一个函数闭包,来保存定时器 timer 状态在内存中
return function () {
// 时间未到清除定时器
// 并建立一个新的定时器
// 利用 setTimeout 时间到了再执行回调
};
}
- 实现主逻辑
function debounce(func, wait) {
// 声明一个定时器
let timer;
// 我们需要返回一个函数闭包,来保存定时器 timer 状态在内存中
return function () {
// 时间未到第二次函数调用就来了,那么清除定时器函数
if (timer) {
clearTimeout(timer)
}
// 利用 setTimeout 时间到了再执行回调
timer = setTimeout(func, wait);
};
}
- 写完整这些特殊的点,当然 2,3你可以一步到位,我分步骤是为了举例,其他复杂题最好清楚分出步骤。
待完善点:
- 我们需要将
this
指向正确的对象 - 我们函数有些默认参数需要传递,比如 JavaScript 在事件处理函数中会提供事件对象 event 也就是 (e)
function debounce(func, wait = 1000) {
let timer;
return function () {
// 这里利用词法作用域,保存了 this ,在闭包中传入,我猜你用过 that 这个名
let context = this;
// 所以我们也要保存下 arguments
let args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
// 用 apply 显示绑定 this
func.apply(context, args);
// 其实就是 context.func(args)
}, wait);
};
}
- 上面基本结束,如果面试官再给你点拓展,就临场发挥下,不要慌写出来更好,写不出拉倒,比如加个 立即执行, 取消功能什么的
需求: 不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
function debounce(func, wait = 1000, immediate = false) {
var timeout, result;
var debounced = function () {
var context = this;
var args = arguments;
if (timeout) {
clearTimeout(timeout);
}
// immediate 关键改动
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) {
result = func.apply(context, args)
}
} else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
}
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
// 可以这样使用
document.getElementById("button").addEventListener('click', function(){
setUseAction.cancel();
})
此时注意一点,就是 userHighRequencyAction
函数可能是有返回值的,所以我们也要返回函数的执行结果,但是当 immediate 为 false 的时候,因为使用了 setTimeout ,我们将 func.apply(context, args) 的返回值赋给变量,最后再 return 的时候,值将会一直是 undefined,所以我们只在 immediate 为 true
的时候返回函数的执行结果。
5. 函数节流 (throttle)
是什么
简单来说 就是在指定的时间间隔内,只允许我们的函数执行一次
。
比如一个事件在被疯狂触发,本来每秒执行几百次(回调)函数,而你使用函数节流设了个时间间隔 1s,那么这个函数在 1s 内只会执行一次
。
简单手写实现
实现
有了上面防抖的例子,节流简单说,原理类似
简单来说,当触发事件的时候,设置一个 timer
, 再次触发事件的时候 timer
存在(不为null),则不执行,直到函数执行了,把timer置空,并启动设置下一个定时器。这也就保证了 wait
时间内函数只会执行一次。
function throttle(func, wait = 1000) {
let timer, context, args;
return function() {
context = this;
args = arguments;
if (!timer) {
timer = setTimeout(function() {
// 执行后置定时器变量为null
timer = null;
func.apply(context, args)
}, wait)
// func.apply(context, args) 想立即执行就放这
}
}
}
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友
Or 搜索我的微信号infinity_9368
,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文
,验证消息请发给我
presious tower shock the rever monster
,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧