面试官:JavaScript执行机制中的闭包?

前言

JavaScript 中的闭包指的是一个函数以及其捆绑的周边环境状态的引用的组合。闭包可以让开发者从内部函数访问外部函数的作用域,即使外部函数已经执行完毕

今天我们通过JavaScript执行机制来聊聊闭包

正文

首先来分析这段代码的执行机制,这段代码我们主要了解作用域链,以及函数的外部作用域outer

outer是根据词法作用域规则生成的

js 复制代码
function bar() {
    console.log(myName);
}
function foo() {
    var myName = 'Tom'
    bar()
}

var myName = 'Jerry'
foo()

在上述代码中,执行 foo 函数时,在 bar 函数内部打印 myName 时,会查找变量 myName

由于 bar 函数内部没有定义 myName ,它会沿着作用域链向上查找。首先在 foo 函数的作用域中查找,foo 函数中定义了 myName'Tom' ,但是 bar 函数调用时,它无法直接访问 foo 函数作用域内的 myName

接着继续向上查找,在全局作用域中找到了定义的 myName'Jerry' ,所以最终打印的是全局作用域中的 myName ,即 'Jerry'

我们来画一下这个的执行流程

  • 调用栈中创建全局上下文执行对象(变量、词法环境)里面有bar=x001指向堆里面(非原始数据类型存放地)foo myName
  • myName最开始是undefined,全局预编译结束,全局执行,myName=Jerry 调用foo
  • 对foo进行预编译,创建AO对象(foo的执行上下文对象)有变量词法环境,myName=undefined,foo里面代码开始执行,
  • myName=Tom,foo变量环境中还有一个outer指针,值指向全局上下文执行对象,为什么指向全局?
  • outer指向谁取决于foo函数所在的词法作用域在哪里(foo声明在哪里),foo在全局作用域中,所以outer指向全局上下文执行对象
  • 这个指向过程就是作用域链,bar开始调用,创建bar执行上下文,入栈,有变量、词法环境,
  • 变量环境中没有声明东西,但是有outer指针,指向全局上下文对象。
  • 然后开始找myName,自己bar中没有找到,然后顺着outer往全局找,找到了jerry因此打印jerry

这里我们还没具体讲解闭包

接下来我们分析这段代码

js 复制代码
function foo() {
    function bar() {
        var age = 18
        console.log(myName);
    }
    var myName = 'Tom'
    
    return bar
}
var myName = 'jerry'
var fn = foo()
fn()

在上述代码中,执行 fn() 时,在 bar 函数内部打印 myName ,首先在 bar 函数内部查找 myName 变量,未找到。

然后沿着作用域链向上查找,在 foo 函数的作用域中找到了定义的 myName'Tom'

而全局作用域中定义的 myName'jerry' ,但由于作用域链的查找顺序,优先使用了 foo 函数作用域中的 myName ,所以最终打印的是 'Tom'

这我们同样分析一下预编译过程

这里我们就要引出闭包了,当bar执行完了,就得被销户,然后就是foo了,可是这里就出现了一个问题,foo返回了一个方法barbarouter指向的foo,但是foo就被销毁了,可是在全局调用了bar,bar是需要使用到foo里的参数的,于是就还是销毁了foo,但是留下了bar需要的参数,以及他的outer,形成了小书包,这就是闭包

闭包有许多的好处:

  1. 实现数据私有化和封装
    通过闭包,可以将一些变量和函数隐藏在内部函数中,外部无法直接访问和修改,从而实现了一定程度的封装和数据保护。
  2. 保持函数执行的上下文和状态
    闭包能够让函数记住其创建时的环境状态,即使外部函数已经执行完毕,内部函数仍然可以访问和操作这些状态信息。这对于实现需要保持状态的功能非常有用,比如计数器、缓存等。
  3. 模拟私有方法
    在 JavaScript 中没有真正的私有方法,但可以利用闭包来模拟私有方法,只在特定的函数内部可访问和操作。
  4. 函数柯里化和偏函数应用
    闭包有助于实现函数柯里化和偏函数应用,使函数的参数传递更加灵活和可定制。
  5. 模块模式
    可以使用闭包创建模块,模块中的变量和方法只在模块内部可访问,对外只暴露必要的接口。

那么让我们分析一下这段代码

js 复制代码
function add() {
    let count = 0;
    function fn() {
        count++
        return count;
    }
    return fn;
}
var res = add();
console.log(res());
console.log(res());
console.log(res());

add函数的调用是不依赖全局变量的,封装函数 实现累加

add 函数返回了内部函数 fn

当执行 var res = add(); 时,res 接收到了 fn 函数,并且 fn 函数形成了一个闭包,能够访问到 add 函数中的 count 变量。

第一次执行 console.log(res()); 时,count 自增为 1 并返回,所以打印 1

第二次执行 console.log(res()); 时,count 再次自增为 2 并返回,所以打印 2

第三次执行 console.log(res()); 时,count 继续自增为 3 并返回,所以打印 3

总结

本文讲解了JavaScript的执行机制和JavaScript执行机制中的闭包,相信看到这里的你一定会有收获的!!!!

相关推荐
你挚爱的强哥17 分钟前
✅✅✅【Vue.js】sd.js基于jQuery Ajax最新原生完整版for凯哥API版本
javascript·vue.js·jquery
stm 学习ing24 分钟前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
前端Hardy1 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189111 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
湫ccc1 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe2 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin2 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
Ysjt | 深3 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++