第一篇介绍了防抖节流函数的原理和常见应用场景,接下来我们来看看具体实现。
这次先介绍防抖函数。
underscore.js中它的源码如下:
_.debounce = function(func, wait, immediate) {
var timeout, result;
var later = function(context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
var debounced = restArguments(function(args) {
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
} else {
timeout = _.delay(later, wait, this, args);
}
return result;
});
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
你会发现里面有一个函数我们还没了解它的实现,首先我们先来看看这个函数restArguments
的源码。
var restArguments = function(func, startIndex) {
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
switch (startIndex) {
case 0: return func.call(this, rest);
case 1: return func.call(this, arguments[0], rest);
case 2: return func.call(this, arguments[0], arguments[1], rest);
}
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
};
是不是觉得少了点什么?嗯,少了注释!
先别着急,我们来看看这个函数的作用是什么。
在ES6中,我们可以使用rest接受函数的剩余参数,并且以数组的方式呈现。
function func(a, ...rest) {
// ...
}
func(1, 2, 3, 4);
// 其中a参数值为1,rest参数为 [2, 3, 4]
在underScore中,因为不确定执行的环境,underScore内部自己实现了一个restArguments方法,就是用es5的方式来实现,可以让我们可以在大多数浏览器环境下使用剩余参数语法。
使用方法:
function func(a, ...rest) {
// ...
}
const resFun = _.restArguments(func);
resFun(1, 2, 3, 4);
// 其中a参数值为1,rest参数为 [2, 3, 4]
以下配上注释啦!
function test(a, b) {
// ...
}
test(1,2,3,4)
console.log(test.length) // 3
var restArguments = function(func, startIndex) {
// func.length是形参的个数,以上面的例子的话是2。
// 已经有startIndex的话就直接用,没有的话就用func.len找到最后参数所在的位置
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
// arguments.length是实际参数的个数,以上面的例子的话是4
// 有可能实际参数大于形式参数,所以arguments.length - startIndex可能小于0,下面取最大值是防止出现负值的情况
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
// 循环拿到剩余参数数组
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
// 接下来要把参数传递给func,也就是执行该函数原本的功能
// 使用call方法实现
switch (startIndex) {
// 剩余参数前面没有参数
case 0: return func.call(this, rest);
// 剩余参数前面有一个参数
case 1: return func.call(this, arguments[0], rest);
// 剩余参数前面有两个参数
case 2: return func.call(this, arguments[0], arguments[1], rest);
}
// 你会发现上面使用call方法的时候如果剩余参数前面剩余的参数很多,是没办法一个一个传递的!
// 使用apply方法实现,需要把剩余参数数组和剩余参数前面的参数放在一个数组里面。
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
};
// 最后!你一定发现了,竟然可以用apply解决,干嘛还要多此一举写多个call方式?
// 其实,这里做了一个性能优化,因为call的性能比apply的高。
OK,从上面我们可以了解到restArguments函数,给它传递一个函数以及一个多余参数开始索引(startIndex)作为参数,它会返回一个函数,我们在调用返回的函数时,开始索引之后的多余参数会被放入到数组中,然后一并传递给restArgs的第一个参数函数调用(作为最后一个参数)。
有时候我们所写的函数不确定有多少个要传递的参数,这样在函数内部实现参数处理时就会比较棘手。这时候使用这个函数进行处理之后,可以把这些剩余的参数组合成数组添加到第一个参数的尾部。
接下来我们来具体看看debounce函数吧!
// 返回一个定时器延迟函数,这个看看就可以知道了吧!
_.delay = restArguments(function(func, wait, args) {
return setTimeout(function() {
return func.apply(null, args);
}, wait);
});
_.debounce = function(func, wait, immediate) {
// 定义定时器和返回结果
var timeout, result;
// 清空定时器,执行函数
var later = function(context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
// 关键实现
var debounced = restArguments(function(args) {
// 如果已经有定时器了,先清空
if (timeout) clearTimeout(timeout);
if (immediate) { // 如果立即执行参数是true,表明先立刻执行一次
// 定义callNow,根据它判断要不要直接执行一次,跟timeout定时器绑定起来,是为了避免在第一次立即执行之后,已经有定时器了,但函数会一直触发执行。
var callNow = !timeout;
// 设置定时器延迟指定时间执行
timeout = setTimeout(later, wait);
// 如果需要先立即执行一次,现在是第一次触发,那么立即执行函数一次
if (callNow) result = func.apply(this, args);
} else { // 如果立即执行参数是false
// 重置定时器,延迟指定时间执行
timeout = _.delay(later, wait, this, args);
}
// 如果func函数本身是有返回值的,也要返回去
return result;
});
// 如果我的wait延迟时间很长!我等不及想取消这个函数的执行!这时候就可以调用cancel取消掉啦
debounced.cancel = function() {
clearTimeout(timeout); // 清空定时器
timeout = null; // 防止内存泄漏
};
// 返回处理后的函数对象
return debounced;
};
嗯,明天再更throttle函数~
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!