当面试官再问我JS闭包时,我能答出来的都在这里了。

简介: 闭包(Closure)是前端面试中的高频考点,广泛应用于函数式编程中。它不仅指函数内部定义的函数,还涉及内存管理、作用域链和垃圾回收机制。闭包可以让函数访问其外部作用域的变量,但也可能引发内存泄漏等问题。通过合理使用闭包,可以实现模块化、高阶函数和回调函数等应用场景。然而,滥用闭包可能导致代码复杂度增加、调试困难以及潜在的性能问题。为了避免这些问题,开发时应谨慎处理闭包,避免不必要的嵌套,并及时清理不再使用的变量和监听器。

闭包(Closure)作为前端面试经常被考察到的高频面试题,可谓是八股文常客。但答案常常参差不齐,简单一点说是函数中的函数,再深入一点提一下内存泄漏,还可以引申到作用域、垃圾回收、模块化等知识点。

闭包的定义

       闭包(Closure)是所有函数式编程中都存在的概念,或叫做现象,并不是JS特有。不仅是JS,函数式编程中都存在闭包。

广义闭包

       广义闭包指的是函数式编程闭包,闭包通用的定义是函数+外层作用域(词法作用域),也可以理解为任何函数都是闭包。

狭义闭包

       狭义闭包指的是JS中的闭包。浏览器出于的性能考虑,对闭包的定义做了优化,当外部作用域有用到这个函数中的变量就会建立闭包,否则不建立。

       所以,在ECMA标准中就加了一个前提条件,在一个函数环境中声明的函数才能称为闭包,那么,函数+外层作用域就是闭包的构成。【闭包MDN文档】

简单示例

function a() {
  var v1 = 1;
  function b() {
    return v1;
  }
  return b;
}
const c = a();

c不能直接访问b函数,但可以通过a函数间接访问b,拿到变量v1的值。意味着b函数不销毁,b函数的词法作用域也无法销毁。

機内存.png

应用场景

早期ES5的模块化

       通过函数中定义函数的方式,实现私有化内部函数。

var exportModule = (function() {
  var a = 1;
  var b = 2;
  function f1() {
    return a;
  }
  function f2() {
    console.log(‘f2’);
  }
});

高阶函数

       将函数作为参数传递给另一个函数,做一些比较复杂的交互场景。

function a() {
  console.log(‘abc’);
}
function b(fn) {
  fn();
}
b(a); // 输出:abc

回调函数

       在早期没有Promise,没有async/await时,关于异步编程常用回调函数处理接口返回数据的操作。

function getData(cb) {
  setTimeout(function() {
    cb(‘data’);
  }, 1000); // 模拟请求数据
}
getData(function(res) {
  console.log(res); // 输出:data
});

优缺点

优势

  • 保存变量:使变量留在内存中不被销毁,从而实现某种功能交互。
  • 封装数据:函数包函数可以建立数据的私有作用域,使数据不暴露给全局,提高数据安全性。

劣势

  • 复杂度提升:当出现函数套函数的情况,无论是代码复杂度还是逻辑复杂度都会上升,带来高昂的调试成本。
  • 内存泄露:如果滥用闭包可能导致不可控的内存泄露,带来潜在风险,影响应用的性能。
  • 心智负担:使用闭包时为了权衡功能和性能,需要考虑何时销毁,带来的负面影响等。

关于内存泄露

我们老说内存泄露,有几个变量占用内存就叫内存泄露吗?这也太夸张了吧!内存泄漏并不是怕多存几个变量或者多存几个函数,以现代的硬件配置不值一提。

       其实最害怕的是关联词法作用域无法销毁导致的不可控扩大内存泄漏,以及穿插污染带来的深层次不易调试的逻辑性错误。内存泄露大致有这两种情况:

1、函数持有了本该被销毁的函数,造成了关联的词法作用域无法被销毁,导致内存泄露的扩大。

       虽然一个函数以及几个变量占用的内存空间有限,但关联的词法作用域中拥有的变量较多,易造成不可控的影响。

const el = document.querySelector(.content-box’);
const handleClick = () => {
  console.log(‘handleClick’);
};
el.addEventListener(‘click’, handleClick);
el.removeEventListener(‘click’, handleClick);

2、多函数共享词法作用域时,可能导致词法作用域留存无法销毁,从而出现词法作用域的某些变量无法访问也无法销毁的情况。

function a() {
  var v1 = ‘v1’;
  var v2 = ‘v2’;
  function f1() {
    return v1;
  } 
  function f2() {
    return v2;
  } 
  return f2;
}
const c = a();

这种情况下,可以通过a函数访问f2函数,间接访问到v2变量。但v1相当于闭塞在a函数内部,无法通过任何方式访问,v1本该被销毁,但实际还存在。

Pasted Graphic 71.png

       因为f1和f2共用了一个词法作用域,v2是有引用的,所以词法作用域不会被销毁,那么v1也存在了。

避免内存泄露

  1. 养成好习惯不滥用闭包,不要遇事不决就函数套函数。
  2. 当词法作用域中的变量不再使用,将其置null,便于垃圾回收标记识别。
  3. 监听器、timer等使用时不要忘记调用remove、clear等方法。
  4. 全局变量相当于在整个生命周期中滞留的内存变量,谨慎使用。

temp1.png

目录
打赏
0
13
16
2
54
分享
相关文章
JavaScript闭包深入剖析:性能剖析与优化技巧
JavaScript 闭包是强大而灵活的特性,广泛应用于数据封装、函数柯里化和事件处理等场景。闭包通过保存外部作用域的变量,实现了私有变量和方法的创建,提升了代码的安全性和可维护性。然而,闭包也可能带来性能问题,如内存泄漏和执行效率下降。为优化闭包性能,建议采取以下策略:及时解除对不再使用的闭包变量的引用,减少闭包的创建次数,使用 WeakMap 管理弱引用,以及优化闭包结构以减少作用域链查找的开销。在实际开发中,无论是 Web 前端还是 Node.js 后端,这些优化措施都能显著提升程序的性能和稳定性。
134 70
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
闭包是JavaScript中不可或缺的部分,它不仅可以增强代码的可维护性,还能在模块化、回调处理等场景中发挥巨大作用。然而,闭包的强大也意味着需要谨慎使用,避免潜在的性能问题和内存泄漏。通过对闭包原理的深入理解以及在实际项目中的灵活应用,你将能够更加高效地编写出简洁且功能强大的代码。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
js 闭包的优点和缺点
【10月更文挑战第27天】JavaScript闭包是一把双刃剑,在合理使用的情况下,它可以带来很多好处,如实现数据封装、记忆功能和模块化等;但如果不注意其缺点,如内存泄漏、变量共享和性能开销等问题,可能会导致代码出现难以调试的错误和性能问题。因此,在使用闭包时,需要谨慎权衡其优缺点,根据具体的应用场景合理地运用闭包。
165 58
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
本文介绍了JSONP的工作原理及其在解决跨域请求中的应用。首先解释了同源策略的概念,然后通过多个示例详细阐述了JSONP如何通过动态解释服务端返回的JavaScript脚本来实现跨域数据交互。文章还探讨了使用jQuery的`$.ajax`方法封装JSONP请求的方式,并提供了具体的代码示例。最后,通过一个更复杂的示例展示了如何处理JSON格式的响应数据。
77 2
[JS]面试官:你的简历上写着熟悉jsonp,那你说说它的底层逻辑是怎样的?
js 闭包
【10月更文挑战第27天】JavaScript闭包是一种强大的特性,它可以用于实现数据隐藏、记忆和缓存等功能,但在使用时也需要注意内存泄漏和变量共享等问题,以确保代码的质量和性能。
58 7
JavaScript闭包:解锁编程潜能,释放你的创造力
【10月更文挑战第25天】本文深入探讨了JavaScript中的闭包,包括其基本概念、创建方法和实践应用。闭包允许函数访问其定义时的作用域链,常用于数据封装、函数柯里化和模块化编程。文章还提供了闭包的最佳实践,帮助读者更好地理解和使用这一强大特性。
43 2
深入理解JavaScript的闭包(Closures)
深入理解JavaScript的闭包(Closures)
60 0

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等