轻松入门JavaScript闭包:从零开始

490 阅读6分钟

摘要:

JavaScript闭包是一个重要且常见的概念,它在编写高质量的JavaScript代码以及框架搭建中有着举足轻重的作用。本文将从一个初学者小白的视角探讨JavaScript闭包的基本概念,提供简单的例子和实际应用,以帮助初学者更好地理解和使用闭包。

正文:

1. 调用栈 

在了解闭包是什么之前,我们需要知道一份代码在JS执行引擎中是如何被执行的,这里就不得不提到一个概念:调用栈。调用栈是一种用于管理函数调用关系的数据结构。众所周知,JS有着临时抱佛脚的特性,JS中的函数在没有调用之前是绝对不会被编译的,直到被编译的前一刻,被调用的函数才会开始编译,就像小时候你的暑假作业,在假期的最后一天才会被拿出来。函数编译时,函数的执行上下文会作为一个整体入栈,然后在栈内编译,当整个函数都执行完毕,则会出栈,相当于在内存中被删除。

2. 什么是闭包 

在前文中我们提到了当一段函数的执行上下文被执行完毕时,这段函数的执行上下文会出栈(注意这段话没有错,一定一定会出栈,天王老子来了也不好使),但是,问题在于,如果这段函数内存在一个或多个在外部才会被调用的值,那么,该如何找到这个值呢?

function foo() {
    var myName = '涵涵'
    let test1 = 1
    let test2 = 2
    var innerBar = {
        getName: function () {
            console.log(test1);
            return myName
        },

    }
    return innerBar
}
var bar = foo()

console.log(bar.getName());

很明显,在这段代码中,innerBar是一个在foo内部的函数,当代码执行到第14行时,foo已经执行完毕,其执行上下文已经出栈,但是,在16行时,innerBar作为一个对象返回给了bar,并且,在随后的17,18行代码中分别输出了foo内的变量myName和test1,如果说foo已经完全出栈,其内部的所有东西都已经被完全从内存中移除,那么这里是不会得到myName和test1的值的,但实际情况却是我们完整得到他们的值,这两个没有随着foo一起烟消云散的值,就是闭包。

闭包.png

在js中,根据词法作用域的规则,内部函数总是可以访问其外部作用域的变量,当内部函数被返回到外部函数之外的时候,即使外部函数执行完毕,从调用栈里面删除了,但内部函数引用了外部函数的变量,那么这些被引用的变量依旧会被保存在调用栈中,而不会随着外部函数一起销毁,我们把这些变量的集合称为闭包

 

闭包2.png

 

 

3. 闭包的简单理解 

简单来说,闭包就是你妈喊你回家吃饭,但你在夜店high到凌晨两点,回家后剩的饭就是闭包。我们可以将外部函数理解成一个体贴慈祥老母亲,内部函数是个逆子,外部函数执行上下文里面的内容就是老母亲做的一桌子菜。通常情况下,逆子会吃饱喝足去happy,老母亲就可以放心地把残羹剩饭清理掉,但如果直到晚饭时间(外部函数的执行上下文)结束,逆子依旧没有回家吃饭(内部函数没有被调用),老母亲就会将逆子爱吃的东西(内部函数需要用到的变量)留下来,留下来的这些东西就是闭包

4. 闭包优缺点及使用场景

JavaScript 是最著名的支持闭包的编程语言之一,闭包的出现在方便诸多大佬写项目的同时,也烧掉了不少和我一样的小白的头发,为了保住为数不多的几根毛,搞清楚闭包的优缺点以及在合适的地方使用它们是十分有必要的

优点:

1.模块化开发:闭包使得创建模块化的代码变得更加容易。通过将相关的变量和函数封装在一个闭包内部,可以避免全局命名冲突,并且可以将模块作为一个独立的单元进行开发、维护和测试。这一优点为框架的搭建以及大型项目的开发提供了很大便利

2.保存状态:闭包可以保存函数执行时的上下文环境,包括函数的变量和参数。这使得函数可以“记住”之前的状态,并且在后续的调用中使用这些状态。这在一些特定的场景下非常有用,比如计数器、事件处理程序等。

缺点:

  1. 内存管理:闭包会保持对外部作用域的引用,可能导致内存泄漏问题(ps:内存泄漏指的不是你的内存条破了个洞然后内存颗粒像开闸放水那样哗哗往外流,而是调用栈存的东西太多导致剩余可用内存减少)。如果不恰当地使用闭包或者忘记释放对外部作用域的引用,会导致不必要的内存占用,就像慈祥的老母亲留的残羹剩饭太多了会占掉冰箱的空间,从而影响性能和资源消耗。

  2. 性能考虑:由于闭包会捕获外部作用域中的变量,可能导致额外的内存消耗和执行时间。在处理大量数据或者频繁使用闭包的场景下,应该注意性能问题,并进行优化。

  3. 理解和调试复杂:闭包涉及到作用域链的概念,对于我这样的菜鸟来说可能比较难以理解。同时,当闭包嵌套层级较深时,调试和排查问题可能会变得复杂和困难。

结尾:

这篇文章旨在帮助初学JavaScript的萌新小白理解和应用闭包,闭包是一种强大而灵活的特性,它能够带来诸多优势,如数据私密性、模块化开发和状态保存。但同时,也需要小心管理内存、处理性能问题。合理使用闭包,可以提高代码的可读性和可维护性,但过度或不恰当地使用闭包可能会带来一些潜在的问题。愿本文对初学者的JavaScript开发之旅有所帮助