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

在上一篇文章✨从预编译到闭包(一)---预编译【学习笔记】✨ - 掘金 (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

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

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

相关推荐
栈老师不回家15 分钟前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙21 分钟前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠25 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds1 小时前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
阿伟来咯~2 小时前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端2 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨2 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
bysking3 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js