JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南

简介: 闭包是JavaScript中不可或缺的部分,它不仅可以增强代码的可维护性,还能在模块化、回调处理等场景中发挥巨大作用。然而,闭包的强大也意味着需要谨慎使用,避免潜在的性能问题和内存泄漏。通过对闭包原理的深入理解以及在实际项目中的灵活应用,你将能够更加高效地编写出简洁且功能强大的代码。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~


作者:watermelo37

涉及领域:Vue、SpingBoot、Docker、LLM、python等

-----------------------------------------------------------------------------------

-------温柔地对待温柔的人,包容的三观就是最大的温柔。-------

-----------------------------------------------------------------------------------

image.gif 编辑

闭包的各种实践场景:高级技巧与实用指南

       闭包在很多现代编程语言中都存在。常见支持闭包的语言有 JavaScript、Python、Ruby、Swift、Kotlin、Scala 等。本文将着重讲在JavaScript中闭包的常见用法及实操意义。

       在JavaScript中,闭包(Closure)是一个非常重要的概念,几乎贯穿于日常开发的方方面面。尽管它强大且用途广泛,但对于初学者而言,理解闭包的原理和实际应用常常是一个挑战。在这篇文章中,将通过理论与实践相结合的方式,帮助你全面理解闭包的本质、原理及其在实际项目中的应用。

image.gif 编辑

一、什么是闭包?

1、闭包的基本概念

       闭包可以简单理解为:一个函数和其词法环境的组合。当一个函数能够记住并访问其词法作用域,即使函数在其词法作用域之外执行,这个函数就被称为闭包。

       在JavaScript中,函数在创建时会形成一个包含函数内部变量和外部环境的闭包。这意味着,闭包可以“记住”其创建时的上下文,并能在稍后调用时访问这些变量。

2、闭包的工作原理

       要理解闭包的工作原理,我们首先需要理解JavaScript的执行上下文和作用域链。当一个函数在另一个函数内部被定义时,它会包含对外部函数变量的引用。这些引用在外部函数执行完毕后不会被销毁,而是被闭包所保留。

function outerFunction() {
    let outerVariable = 'I am from outer scope';
    function innerFunction() {
        console.log(outerVariable);
    }
    return innerFunction;
}
const closure = outerFunction();
closure();  // 输出: I am from outer scope

image.gif

       在上面的例子中,innerFunction保留了对outerVariable的访问权,即使outerFunction已经执行完毕。这就是闭包的基本特性。

3、闭包的用途

  • 数据封装和私有化(模拟私有变量)
  • 维持变量初始状态
  • 柯里化(Currying)
  • 函数工厂(创建函数的函数)

二、闭包的实际应用场景

       闭包不仅仅是一个理论概念,在实际开发中它有很多重要的应用场景。

1、模拟私有变量

       JavaScript中没有私有变量的概念,但通过闭包可以模拟出类似私有变量的效果。

function createCounter() {
    let count = 0;
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        }
    };
}
const counter = createCounter();
console.log(counter.increment());  // 输出: 1
console.log(counter.increment());  // 输出: 2
console.log(counter.decrement());  // 输出: 1

image.gif

       在这个例子中,count变量无法直接从外部访问,只能通过increment和decrement方法进行操作。这种方式在编写模块化代码时尤为有用。

2、事件处理和回调函数

       闭包在事件处理和回调函数中非常常见,尤其是在需要保持状态或访问外部变量的情况下。

function setupClickHandlers() {
    const buttons = document.querySelectorAll('button');
    for (let i = 0; i < buttons.length; i++) {
        buttons[i].addEventListener('click', function() {
            console.log('Button ' + i + ' clicked');
        });
    }
}
setupClickHandlers();

image.gif

       每个按钮点击时,都会输出其对应的索引。这是因为闭包“记住”了当时的 i 值。

3、延迟函数和异步操作

       闭包在处理延迟函数或异步操作时也非常有用。

for (var i = 1; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, i * 1000);
}
// 通过闭包解决变量提升问题
for (var i = 1; i <= 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j);
        }, j * 1000);
    })(i);
}

image.gif

       在第一个例子中,所有的setTimeout函数都会输出5,因为它们共享了同一个i变量。而在第二个例子中,通过闭包创建了新的作用域,每个setTimeout函数都有自己的j变量,从而正确输出1到5。

4、柯里化

       柯里化(Currying)是一种将接受多个参数的函数转换成一系列接受单个参数的函数的技术。这种技术以数学家哈斯凯尔·柯里(Haskell Curry)的名字命名。在柯里化过程中,原函数被转换成一个函数,这个函数接受一个参数并返回一个新的函数,后者再次接受下一个参数,依此类推,直到收集完所有需要的参数。

function add(x) {
  return function(y) {
    return x + y;
  };
}
const addFive = add(5);
console.log(addFive(3)); // 输出 8

image.gif

       在这个例子中,add 函数返回一个新的函数,它记住了 x 的值,并接受另一个参数 y 来完成加法。

5、备忘录模式(Memoization)

       备忘录模式特别适用于递归算法,如计算斐波那契数列,或者任何重复调用相同参数的递归函数。以下是使用闭包实现备忘录模式的一个例子。

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = args.toString(); // 将参数列表转换为字符串作为缓存键
    if (key in cache) {
      return cache[key];
    } else {
      const result = fn.apply(this, args);
      cache[key] = result;
      return result;
    }
  };
}
const fibonacci = (n) => {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
};
const memoizedFibonacci = memoize(fibonacci);
console.log(memoizedFibonacci(10)); // 使用备忘录快速计算

image.gif

       在这个例子中,memoize 函数是一个高阶函数,它接收一个函数 fn 作为参数,并返回一个新的函数。新函数会检查传入的参数是否已经在缓存 cache 中,如果是,则直接返回缓存的结果;如果不是,则调用原始函数 fn,将结果存入缓存,并返回结果。

三、闭包的性能问题

       虽然闭包功能强大,但滥用闭包可能会带来性能问题。每次创建闭包时,都会在内存中保留一份变量的引用,过多的闭包可能导致内存泄漏。因此,在编写代码时应谨慎使用闭包,避免在不必要的场合创建闭包。

1、内存泄漏

       不当的闭包使用会导致内存泄漏,特别是在循环中创建大量闭包时。如果闭包引用了不再需要的变量或对象,内存将无法释放。

function leakyFunction() {
    let largeData = new Array(1000000).join('*');
    return function() {
        console.log(largeData.length);
    };
}
const leaky = leakyFunction();
// largeData 仍然保存在内存中,即使它不再被需要

image.gif

       在上述例子中,largeData占用了大量内存,即使在函数执行完毕后也不会被释放。因此,尽量避免在闭包中引用不必要的变量。

四、最佳实践与建议

       为了更好地使用闭包,以下是一些最佳实践和建议:

  1. 控制闭包的数量:不要在不必要的场合创建闭包,尤其是在高频调用的函数中。
  2. 释放不再需要的变量:如果闭包引用了大量数据,应当在适当时候释放这些数据,以避免内存泄漏。
  3. 使用let或const代替var:在使用闭包时,let或const声明的变量具有块级作用域,能更好地避免变量提升问题。
  4. 理解作用域链:深入理解JavaScript的作用域链和执行上下文,有助于更好地掌握闭包的使用。

五、总结

       闭包是JavaScript中不可或缺的部分,它不仅可以增强代码的可维护性,还能在模块化、回调处理等场景中发挥巨大作用。然而,闭包的强大也意味着需要谨慎使用,避免潜在的性能问题和内存泄漏。通过对闭包原理的深入理解以及在实际项目中的灵活应用,你将能够更加高效地编写出简洁且功能强大的代码。

       只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

       更多优质内容,请关注:

       你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解

       通过array.filter()实现数组的数据筛选、数据清洗和链式调用

       el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能

       极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图

       shpfile转GeoJSON且控制转化精度;如何获取GeoJSON?GeoJson结构详解

       Docker 入门全攻略:安装、操作与常用命令指南

       通过array.reduce()实现数据汇总、条件筛选和映射、对象属性的扁平化、转换数据格式等

       巧用Array.forEach:简化循环与增强代码可读性

       Mapbox添加行政区矢量图层、分级设色图层、自定义鼠标悬浮框、添加天地图底图等

       管理数据必备!侦听器watch用法详解

相关文章
|
3天前
|
前端开发 JavaScript Java
【Java进阶】JavaScript电灯开关实例:从理论到实践
这个例子展示了JavaScript的基本功能,包括操作HTML元素,监听事件,以及改变元素的样式。通过学习和理解这个例子,你可以了解到JavaScript在网页中的应用,以及如何使用JavaScript来创建交互式的网页。
28 13
|
2月前
|
前端开发 JavaScript Java
JavaScript闭包深入剖析:性能剖析与优化技巧
JavaScript 闭包是强大而灵活的特性,广泛应用于数据封装、函数柯里化和事件处理等场景。闭包通过保存外部作用域的变量,实现了私有变量和方法的创建,提升了代码的安全性和可维护性。然而,闭包也可能带来性能问题,如内存泄漏和执行效率下降。为优化闭包性能,建议采取以下策略:及时解除对不再使用的闭包变量的引用,减少闭包的创建次数,使用 WeakMap 管理弱引用,以及优化闭包结构以减少作用域链查找的开销。在实际开发中,无论是 Web 前端还是 Node.js 后端,这些优化措施都能显著提升程序的性能和稳定性。
142 70
|
2月前
|
自然语言处理 JavaScript 前端开发
当面试官再问我JS闭包时,我能答出来的都在这里了。
闭包(Closure)是前端面试中的高频考点,广泛应用于函数式编程中。它不仅指函数内部定义的函数,还涉及内存管理、作用域链和垃圾回收机制。闭包可以让函数访问其外部作用域的变量,但也可能引发内存泄漏等问题。通过合理使用闭包,可以实现模块化、高阶函数和回调函数等应用场景。然而,滥用闭包可能导致代码复杂度增加、调试困难以及潜在的性能问题。为了避免这些问题,开发时应谨慎处理闭包,避免不必要的嵌套,并及时清理不再使用的变量和监听器。
146 16
当面试官再问我JS闭包时,我能答出来的都在这里了。
|
1月前
|
JavaScript 前端开发 Java
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
柯里化是一种强大的函数式编程技术,它通过将函数分解为单参数形式,实现了灵活性与可复用性的统一。无论是参数复用、延迟执行,还是函数组合,柯里化都为现代编程提供了极大的便利。 从 Redux 的选择器优化到复杂的数据流处理,再到深度嵌套的函数优化,柯里化在实际开发中展现出了非凡的价值。如果你希望编写更简洁、更优雅的代码,柯里化无疑是一个值得深入学习和实践的工具。从简单的实现到复杂的应用,希望这篇博客能为你揭开柯里化的奥秘,助力你的开发之旅! 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
3月前
|
缓存 NoSQL JavaScript
Vue.js应用结合Redis数据库:实践与优化
将Vue.js应用与Redis结合,可以实现高效的数据管理和快速响应的用户体验。通过合理的实践步骤和优化策略,可以充分发挥两者的优势,提高应用的性能和可靠性。希望本文能为您在实际开发中提供有价值的参考。
85 11
|
4月前
|
存储 网络架构
Next.js 实战 (四):i18n 国际化的最优方案实践
这篇文章介绍了Next.js国际化方案,作者对比了网上常见的方案并提出了自己的需求:不破坏应用程序的目录结构和路由。文章推荐使用next-intl库来实现国际化,并提供了详细的安装步骤和代码示例。作者实现了国际化切换时不改变路由,并把当前语言的key存储到浏览器cookie中,使得刷新浏览器后语言不会失效。最后,文章总结了这种国际化方案的优势,并提供Github仓库链接供读者参考。
244 5
|
4月前
纸屑飘落生日蛋糕场景js+css3动画特效
纸屑飘落生日蛋糕CSS3动画特效是一款js+css3制作的全屏纸屑飘落,生日蛋糕点亮庆祝动画特效。
74 3
|
5月前
|
缓存 监控 JavaScript
Vue.js 框架下的性能优化策略与实践
Vue.js 框架下的性能优化策略与实践
|
5月前
|
JavaScript 前端开发 API
Vue.js 3:深入探索组合式API的实践与应用
Vue.js 3:深入探索组合式API的实践与应用
|
5月前
|
缓存 负载均衡 JavaScript
构建高效后端服务:Node.js与Express框架实践
在数字化时代的浪潮中,后端服务的重要性不言而喻。本文将通过深入浅出的方式介绍如何利用Node.js及其强大的Express框架来搭建一个高效的后端服务。我们将从零开始,逐步深入,不仅涉及基础的代码编写,更会探讨如何优化性能和处理高并发场景。无论你是后端新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启示。
下一篇
oss创建bucket