写点什么

优雅编程 | Javascript 闭包的 4 种高级用法

用户头像
devpoint
关注
发布于: 2021 年 03 月 12 日
优雅编程 | Javascript闭包的4种高级用法

函数修饰器是一个高阶函数,它将一个函数作为参数并返回另一个函数,并且返回的函数是参数函数的变体。


提高编程能力最好的方式就是去阅读并学习开源框架或者脚本库,今天我们就来学习underscore.jslodash.jsramda.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开发技术要点");
复制代码


实现这些常见的函数可以帮助我们更好地理解修饰器的工作方式,并让我们了解它可以封装的逻辑类型。


函数修饰器是一种强大的工具,可以在不修改原始函数的情况下创建现有函数的变体。它们可以作为函数编程工具箱的一部分,用于重用公共逻辑。


发布于: 2021 年 03 月 12 日阅读数: 8
用户头像

devpoint

关注

细节的追求者 2011.11.12 加入

专注前端开发,用技术创造价值!

评论

发布
暂无评论
优雅编程 | Javascript闭包的4种高级用法