菜鸟入门第六天——闭包

133 阅读4分钟

前言

今天我们来聊一下JavaScript中的闭包,在进入主题之前,我们先聊一聊调用栈以及作用域链。

调用栈

调用栈一个数据结构,通常以栈的形式组织,用于存储函数的调用信息。

当一个函数被调用时,相关的信息,如函数参数、局部变量和返回地址等,会被压入调用栈的顶部。当该函数执行完毕并返回结果时,相关信息会从栈顶弹出,程序控制流回到调用该函数的地方。

下面我们来看个案例

function greet(name) {
  console.log("Hello, " + name + "!");
}
function sayHello() {
  let myName = "Tom";
  greet(myName);
}
function main() {
  sayHello();
}
main();

运行结果为:

image.png

我们来分析一下这个代码执行流程

1.main() 函数被调用,将其添加到调用栈中。

调用栈:

- main()

2.在 main() 函数内,我们调用 sayHello() 函数。这将导致 sayHello() 函数被添加到调用栈的顶部。

调用栈:

- main()
- sayHello()

3.在 sayHello() 函数内,我们创建一个局部变量 myName 并将其设置为 "Alice"。然后,我们调用 greet(myName) 函数,将其添加到调用栈的顶部。

调用栈:

- main()
- sayHello()
- greet(myName)

4.在 greet(myName) 函数内,我们使用参数 name 拼接问候语,并将其打印到控制台。完成后,greet(myName) 函数从调用栈中弹出。

调用栈:

- main()
- sayHello()

5.sayHello() 函数执行完毕,也从调用栈中弹出。

调用栈:

- main()

6.最后,main() 函数也执行完毕,并从调用栈中弹出。

调用栈:

(空)

作用域链

作用域链是指在编程语言中,特定变量的可访问性和作用域范围的链式结构。它是用于确定在程序中查找和访问变量时的规则,决定了哪些变量在当前上下文中可见和可用。

下面我们来看个案例

function outerFunction() {
  var outerVar = 10;
  function innerFunction() {
    var innerVar = 20;
    console.log(outerVar + innerVar);
  }
  innerFunction();
}
outerFunction();

运行结果为:

image.png

在这个示例中,outerFunction内部包含了outerVar变量,而innerFunction内部包含了innerVar变量。当innerFunction内部尝试访问变量时,它首先查找自己的作用域,如果找不到,就向上查找到外部作用域,即outerFunction的作用域,最终找到了outerVar变量。这就是作用域链的工作原理。

闭包

在js中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量的,当内部函数被返回到外部函数之外时,即使外部函数执行结束了,但是内部函数引用了外部函数的变量,那么这些变量依旧会被保存在内存中,我们把这些变量的集合称为闭包。

下面我们来看个案例

// 闭包
function foo() {
    var myName = '旭旭'
    let test1 = 1
    let test2 = 2
    var innerBar = {
        getName: function () {
            console.log(test1);
            return myName
        },
        setName: function (newName) {
            myName = newName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName('浪哥')
console.log(bar.getName());

运行结果为:

image.png

下面我们来分析原因工作流程

1.创建一个foo执行上下文,变量环境中有myName 并被赋值为'旭旭'。词法环境中有test1 并被赋值为 1,test2 并被赋值为 2。innerBar这个对象返回出去赋给变量bar,到这里foo执行上下文就结束了

2.foo执行上下文的旁边还会有一个内存空间用于存放引用出去的变量(test1和myName)

3.我们调用了 setName 方法,并将 '浪哥' 作为参数传递。这会将 myName 的值从 '旭旭' 修改为 '浪哥'。

4.最后,通过 bar.getName(),我们调用了 getName 方法。在这个方法内部,它打印了 test1 的值,然后返回 myName。

image.png

闭包优点

变量私有化:是将变量和数据封装在某种程度上,以限制其在程序中的可见性和访问性。

闭包缺点

内存泄漏:是在计算机程序中,分配给程序的内存无法被正常释放或回收,导致程序持续占用更多内存资源,最终耗尽系统的可用内存。

总结

闭包是一种非常有用的编程概念,它可以帮助实现封装、模块化,同时允许在函数之间共享数据等,我相信通过我的讲解,可以使你更进一步了解它。