函数修饰器是一个高阶函数,它将一个函数作为参数并返回另一个函数,并且返回的函数是参数函数的变体。
提高编程能力最好的方式就是去阅读并学习开源框架或者脚本库,今天我们就来学习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开发技术要点");
复制代码
实现这些常见的函数可以帮助我们更好地理解修饰器的工作方式,并让我们了解它可以封装的逻辑类型。
函数修饰器是一种强大的工具,可以在不修改原始函数的情况下创建现有函数的变体。它们可以作为函数编程工具箱的一部分,用于重用公共逻辑。
评论