✨从预编译到闭包(二)--- 闭包【学习笔记】✨

在上一篇文章✨从预编译到闭包(一)---预编译【学习笔记】✨ - 掘金 (juejin.cn)中我们学习了关于js预编译的概念,在这篇文章中我们将了解闭包的概念

在了解闭包之前我们先了解一几个小概念

函数声明整体提升与变量声明变量提升

js 复制代码
WuHu()

function WuHu(){
    console.log('芜湖');
}

console.log(fly);

var fly = '起飞'

按直觉来说,代码从上往下一行一行执行的话,这段代码中调用WuHu()时和console.log(fly);时是不能够打印出我们想要的结果的,但是需要了解的是,在js中,函数声明会整体提升 ,用var声明变量,声明提升

所以在js的执行引擎眼中,这段代码是这样子的:

js 复制代码
function WuHu(){
    console.log('芜湖');
}

var fly

WuHu()

console.log(fly);

var fly = '起飞'

可以看到函数的声明和var的声明都芜湖起飞了,它们会芜湖起飞到其所在的执行上下文的最顶端,这样一来,在执行函数调用WuHu()时,便可以打印到芜湖了,在执行console.log(fly);时,由于只能找到变量声明var fly,但不能找到fly的值,所以此时打印为undefined。需要注意的是,打印undefined与报错中的ReferenceError: fly is not defined是不同的,undefined是指能找到这个变量,但是找不到这个变量的值,而报错中的ReferenceError: fly is not defined是指压根找不到这个变量,连变量的声明都找不到。

调用栈

我们来执行这样一段代码

js 复制代码
function foo(){
    console.log('咕噜');
    foo()
}
foo()

放在浏览器中执行之后:

可以看到在打印了10235个咕噜之后,浏览器终究是扛不住了,这里我们便可以引入一个调用栈的概念。 其中RangeError: Maximum call stack size exceeded是指栈溢出了,那么为什么会栈溢出呢,我们先来了解调用栈是干什么的

调用栈

· 是用来管理函数调用关系的一种数据结构

· 当一个函数执行完毕后,它的执行上下文一定会出栈

js 复制代码
var a = 2

function add(b,c){
    return b + c
}

function addAll(b,c){
    var d = 10
    var result = add(b,c)
    return a + result + d
}

addAll(3,6)

在这段代码中,add函数的执行发生在addAll函数中,首先,创建一个全局的执行上下文压入调用栈的栈底,然后在调用addAll函数时,addAll的执行上下文会入栈,在addAll执行过程中,调用了add函数,此时add的执行上下文也入栈

首先add函数会先执行完成,然后出栈,紧接着addAll函数执行完成,出栈,最后所有代码执行完成,全局执行上下文出栈。

可以看出来,调用栈是用来管理函数调用关系的一种数据结构,并且当一个函数执行完毕后,它的执行上下文一定会出栈。所以在刚刚的foo函数的执行中,调用栈中会被持续压入很多很多个执行上下文,当超过调用栈的内存时,就会爆栈

作用域链

通过词法作用域来确定某作用域的外层作用域,查找变量由内而外的这种链状关系,叫做作用域链

js 复制代码
function bar(){
    console.log(myName);
}

function foo(){
    var myName = '龙龙'
    bar()
}

var myName = '君君'
foo()

在这段代码中,全局声明了一个myName为君君,然后调用foo时,在foo中,myName被赋值为龙龙,然后紧接着调用bar,bar执行时会打印myName,那么此时打印结果会是什么呢,这里就要涉及到作用域链了,由于console.log(myName);的词法作用域是bar,所以其外层作用域为全局,所以在打印myName时,由于bar的执行上下文中找不到myName,那么便往外一层的作用域即全局作用域中找myName,而全局中有一个var myName = '君君',所以console.log(myName)打印的结果为君君。而这种通过词法作用域来确定某作用域的外层作用域,查找变量由内而外的这种链状关系,叫做作用域链其实在全局和函数的执行上下文中都会有一个outer来记录该执行上下文外层的作用域,全局由于找不到更外层的作用域了,所以outer为null,而在这个例子中,bar和foo的outer都指向全局执行上下文,所以这两个函数在查找自身没有的变量时,都会到自己outer所指的作用域去查找,即全局执行上下文中查找变量。

闭包

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

js 复制代码
function a(){

    function b(){
        var bbb = 234
        console.log(aaa);
    }

    var aaa= 123
    
    return b
}

var c = a()

c()  //打印结果:123

我们以这段代码来理解闭包,首先a函数的执行结果为一个函数b,并且函数b声明在a内,由于var c = a() 并且调用了c,这其实相当于把a函数内的b函数拿到全局来执行了一遍。那么按照直觉来说,由于b函数在全局被执行,按照作用域的知识,应该会查找不到在a函数内的aaa这个变量,但是当内部函数被返回到外部函数之外时,即使外部函数执行结束了,但是内部函数引用了外部函数的变量,那么这些变量依旧会被保存在内存中,我们把这些变量的集合称为闭包

用调用栈来理解,首先全局执行上下文先入栈,然后执行a函数,a函数的执行上下文入栈,a函数执行完毕后,正常情况下a函数的执行上下文会出栈,但是在a函数内声明了一个b函数,并且b函数在全局中调用,并且需要用到a函数内的aaa变量,此时a函数的执行上下文出栈后会留下一个变量的集合,这个集合里包含了内部函数b引用外部函数a的变量aaa,所以在执行到b函数中的console.log(aaa);时,首先在b函数内没有找到aaa这个变量,便往外层查找,虽然此时a函数的执行上下文出栈了,但是a函数留下了一个闭包,所以可以在闭包中找到aaa = 123,所以执行结果为123

所以闭包其实就是内部函数会引用到外部函数里的变量,但是内部函数被返回到外部函数之外被调用,当外部函数执行完毕并且执行上下文出栈后,会留下一个变量的集合来装下内部函数会引用到外部函数里的变量,这个变量的集合就称之为闭包

如果感觉有收获,别忘了给我点个赞哦

相关推荐
旧味清欢|10 分钟前
关注分离(Separation of Concerns)在前端开发中的实践演进:从 XMLHttpRequest 到 Fetch API
javascript·http·es6
热爱编程的小曾27 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin38 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
-代号95271 小时前
【JavaScript】十四、轮播图
javascript·css·css3
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX187302 小时前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员