函数的防抖与节流以及一些重要的函数模式

防抖(debounce)

在学习单片机开发时,按键防抖非常重要,我们必须使用一定的方法来进行防抖,否则会造成一次按键按键的多次触发的情况。而在JavaScript中,同样会有这样的问题,即在用户进行持续性的操作时,在用户大概率完成后,在进行后续操作(请求服务器,浏览器渲染等)。如果不进行防抖操作,会导致服务器或浏览器的性能浪费。

其具体定义:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

- 如果在一段时间内没有再次触发滚动事件,那么就执行函数
- 如果在一段时间内再次触发滚动事件,那么当前的计时取消,重新开始计时

下面是一个函数实现:

1
2
3
4
5
6
7
8
9
10
11
12
function debounce(fun, delay) {
let timer
return function (arguments) {
//获取函数的作用域和变量
let that = this;
let args = arguments;
clearTimeout(timer) // 清除定时器
timer = setTimeout(function () {
fun.apply(that, args)
}, delay)
}
}

该函数接受一个目标函数与延迟,该函数内部返回了一个函数,并将目标函数的id设为定时器的id。如果第一次调用该函数,会设置一个定时器,设定在delay毫秒后执行目标函数,但是如果在delay毫秒时间内再次调用该函数的防抖函数,那么就会清除掉目标函数的定时器,也就不会执行了。并再次设置一个定时器,循环以上步骤。

节流(Throttle

节流更是一个重要的概念,特别是在进行渲染的操作时(比如,mousemove,scoll),如果不进行节流,那么浏览器将会消耗很多性能,致使页面卡顿与操作不流畅。

具体定义:是指在一定的时间内只允许函数执行特定次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function throttle(fn,delay){
let _self = fn,
timer,
firstTime = true;
return function(){
let args = arguments
_this = this
if(firstTime){
_self.apply(_this, args)
return firstTime = false
}
if(timer){
return false
}
timer = setTimeout(function(){
clearTimeout(timer)
timer = null
_self.apply(_this, args)
}, delay||500)
}
}

该函数的核心是在:在外部先判断目标函数fun的timer属性是否为空。如果为空,则代表一次函数还未执行完,则直接返回。否则则设定目标函数的timer属性为一个定时器,在定时器的函数中将timer清空,并执行目标函数。

2021/2/17

最近在读《JavaScript设计模式与开发实践》,发现除了函数的节流与防抖,还有一些高级函数模式,可以用来解决一些问题。

分时函数

当一个任务需要进行大量同类操作,此时可能会造成页面的卡顿。此时,为了防止页面卡顿,我们要把单个大型任务分割成为多个小的任务,典型的场景是:大量数据插入表格。

我们把但分时函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let timeChunk = function(ary, fn, count){
let obj
let t
let len = ary.length
let start = function(){
for(let i = 0; i < Math.min(count || 1, ary.length); i++){
let obj = ary.shift()
fn(obj)
}
}
return function(){
t = setInterval(function(){
if(ary.length === 0){
return clearInterval(t)
}
start()
}, 200)
}
}

比如我们需要添加1000调信息到页面中,我们采用每次创建8个节点。

1
2
3
4
5
6
7
8
9
10
11
12
let ary = []
for(let i = 1; i <= 1000; i++){
ary.push(i)
}

let renserList = timeChunk(ary, function(n){
let div = document.createElement('div')
dic.innerHTML = n
document.body.appenChild(div)
}, 8)

renderList()

惰性加载函数

当一个任务在执行之前后悔事先执行一些预先性工作,并且这些工作在每次执行时的结果都是一样的,那我们可以在第一次得到结果后就将其凝固,在以后的每一次执行时就可以减少判断次数,提高性能。

一个典型的场景就是浏览器能力检测,常见的写法如:

1
2
3
4
5
6
7
8
let addEvent = function(elem, type, handler){
if(window.addEventListener){
return elem.addEventListener(type, handler, false)
}
if(window.attachEvent){
return elem.attachEvent('on' + type, handler)
}
}

这个函数的缺点是,当它每次被调用的时候都会执行里面的if条件分支,虽然执行这些if分支的开销不算大,但也许有一些方法可以让程序避免这些重复的执行条件。如下面的方案就是在最开始调用一次判断,后续调用该函数时,就不会再进行判断了:

1
2
3
4
5
6
7
8
9
10
11
12
let addEvent = (function(){
if(window.addEventListener){
return function(elem, type, handler){
elem.addEventListener(type, handler, false)
}
}
if(window.attachEvent){
return function(elem, type, handler){
return elem.attachEvent('on' + type, handler)
}
}
})()

第三种方案就是我们的懒加载函数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
let addEvent = function(elem, type, handler){
if(window.addEventListener){
addEvent = function(elem, type, handler){
elem.addEventListener(type, handler, false)
}
}else if(window.attachEvent){
addEvent = function(elem, type, handler){
elem.attachEvent('on'+type, handler)
}
}
addEvent(elem, type, handler)
}

这个函数在第一次调用时,会根据条件分支,对addEvent事件进行重载,并且对其进行调用一次。这个函数不必判断第一次,而是自动在第一次时重载,很妙。

这里有一个知识点是,在具名函数的执行过程中是可以对具名函数进行重新赋值的。

summary

总体来说,在进行某些操作时,比如表单实时验证或者绘图的操作等,函数的防抖与节流会大幅度节省服务器与浏览器的性能!

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :