函数修饰器是一个高阶函数,它将一个函数作为参数并返回另一个函数,并且返回的函数是参数函数的变体。
提高编程能力最好的方式就是去阅读并学习开源框架或者脚本库,今天我们就来学习underscore.js、lodash.js、ramda.js之类的库中利用闭包原理实现函数修饰器,从中受益匪浅。
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 Javascript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
once()
在之前的文章中曾经写过一个类似的函数once()。但是这里实现的功能有点不一样,这里执行一次并返回函数的执行结果。
function once(fn) {
let returnValue;
let canRun = true;
return function runOnce() {
if (canRun) {
returnValue = fn.apply(this, arguments);
canRun = false;
}
return returnValue;
};
}
function process(title) {
console.log({
title,
});
}
const processonce = once(process);
const title = "DevPoint";
processonce(title);
processonce(title);
processonce(title);
复制代码
上面代码执行结果只会输出一次:{ title: 'DevPoint' }
once()
是一个返回另一个函数的函数。返回的函数runOnce()
是一个闭包。同样重要的是要注意原始函数是如何被调用的——通过传入this
的当前值和所有参数arguments
:fn.apply(this, arguments)
。可以应用的场景可以是抢购,如现在比较多的活动抢茅台,可以减少疯狂点击发送请求。
after()
after(count, fn)
:创建仅在多次调用后才执行的函数方法。例如,当想要确保函数只在完成count
次异步操作完成后才运行时,这个函数就非常实用。
function after(count, fn) {
let runCount = 0;
return function runAfter() {
runCount = runCount + 1;
if (runCount >= count) {
return fn.apply(this, arguments);
}
};
}
function end() {
console.log("异步操作结束了!");
}
const endAfter3Calls = after(3, end); // 定义在执行3次异步操作后执行函数logResult
setTimeout(() => {
console.log("=>完成第一次异步操作");
endAfter3Calls();
}, 3000);
setTimeout(() => {
console.log("=>完成第二次异步操作");
endAfter3Calls();
}, 2000);
setTimeout(() => {
console.log("=>完成第三次异步操作");
endAfter3Calls();
}, 6000);
复制代码
上面代码执行输出结果如下:
=>完成第二次异步操作
=>完成第一次异步操作
=>完成第三次异步操作
异步操作结束了!
复制代码
这个方法在前端需要做一系列动画效果的时候很实用。
节流:throttle()
throttle(fn, wait)
:用于限制函数触发的频率,每个delay
时间间隔,最多只能执行函数一次。一个最常见的例子是在监听resize
/scroll
事件时,为了性能考虑,需要限制回调执行的频率,此时便会使用throttle
函数进行限制,也是我们常说的节流。
function throttle(fn, interval) {
let lastTime;
return function throttled() {
const timeSinceLastExecution = Date.now() - lastTime;
if (!lastTime || timeSinceLastExecution >= interval) {
fn.apply(this, arguments);
lastTime = Date.now();
}
};
}
function process() {
console.log("DevPoint");
}
const throttledProcess = throttle(process, 1000);
for (let i = 0, len = 100; i < len; i++) {
throttledProcess();
}
复制代码
防抖:debounce()
debounce(fn, wait)
:可以减少函数触发的频率,但限制的方式有点不同。当函数触发时,使用一个定时器延迟执行操作。当函数被再次触发时,清除已设置的定时器,重新设置定时器。如果上一次的延迟操作还未执行,则会被清除。
function debounce(fn, interval) {
let timer;
const debounced = () => {
clearTimeout(timer);
const args = arguments;
timer = setTimeout(() => {
fn.apply(this, args);
}, interval);
};
return debounced;
}
function process() {
console.log("DevPoint");
}
const delayProcess = debounce(process, 400);
for (let i = 0, len = 100; i < len; i++) {
delayProcess();
}
复制代码
throttle
函数与debounce
函数的区别就是throttle
函数在触发后会马上执行,而debounce
函数会在一定延迟后才执行。从触发开始到延迟结束,只执行函数一次。
partial()
现在创建对所有函数都可用的partial()
方法。这里使用了ECMAScript 6 rest
参数语法…
,而不是参数 arguments
对象,下面实现连接数组和参数不是数组对象。
Function.prototype.partial = function (...leftArguments) {
let fn = this;
return function partialFn(...rightArguments) {
let args = leftArguments.concat(rightArguments);
return fn.apply(this, args);
};
};
function log(level, message) {
console.log(level + " :" + message);
}
const logInfo = log.partial("描述");
logInfo("DevPoint开发技术要点");
复制代码
实现这些常见的函数可以帮助我们更好地理解修饰器的工作方式,并让我们了解它可以封装的逻辑类型。
函数修饰器是一种强大的工具,可以在不修改原始函数的情况下创建现有函数的变体。它们可以作为函数编程工具箱的一部分,用于重用公共逻辑。
评论