从执行上下文和作用域链理解闭包

简介: 从执行上下文和作用域链理解闭包

本文仅用于笔者的学习记录。水平还不够教学(手动狗头)。如果发现错误和有疑惑的地方,欢迎交流学习!!!

在了解闭包之前,我觉得很有必要弄清楚js代码的执行过程和变量存储。


执行上下文和变量存储



浏览器打开一个页面,首先会从计算机的虚拟内存中分配两块内存出来,分别是栈内存堆内存栈内存(ECStack)主要供代码执行,存储声明的变量和原始值类型的值。

堆内存(heap)存储对象类型的值。在堆内存中,还有一个Global Object的全局对象,存储浏览器为js提供的内置api


模型简化如下图所示

image.png


ECStack执行栈



当我们执行一段代码的过程中


var x = [12, 23]
function fn(y) {
   y[0] = 100;
   y = [100]
   y[1] = [200]
}
fn(x)
console.log(x)

这段代码执行的过程中会创建一个全局的执行上下文(EC(G)),全局执行上下文是全局代码执行的一个环境,并供其他上下文中的代码执行。上下文在其所有代码执行完毕后销毁,包括定义在它上面的所有变量和函数,但是全局执行上下文在应用退出或者页面关闭的时候才会被销毁。所以,我们可以知道执行上下文其实就是栈内存中的一段空间。

在代码执行的过程当中,会出现变量的声明。所以在每个上下文都有一个关联的变量对象(variable object),这个上下文中定义的所有变量和函数都存在这个对象上。全局执行上下文则存储全局的变量对象。


上下文中的代码在执行的过程当中,如果上下文是函数,则其活动对象(activation object)用作变量对象。简单理解就是活动对象中存储了这个函数执行需要的变量,包括传入的形参和函数中的私有变量。


附:ECMAScript规范指出独立作用域只能通过“函数(function)”代码类型的执行上下文创建,ECMAScript里的for循环并不能创建一个局部的上下文。(想起来一道经典的题目,就是在for循环中把var改成let)


回归正题,简单总结一下上述的名词含义


  • 执行环境栈(ECStack):专门用来供代码执行的 栈内存
  • 全局对象(GO):存放内置的属性方法,window 指向
  • 全局执行上下文( EC(G)  ):页面加载后进栈、销毁后出栈
  • 变量对象VO(Variable Object) : 存放当前执行上下文中创建的变量和值
  • 活动对象AO(Activation Object) : 函数私有上下文中的变量对象


现在模型变成下图所示image.png



作用域链



上下文中的代码在执行过程中,会创建变量对象的一个作用域链。作用域链主要是提供一个查找机制。比如私有上下文中遇到一个变量,他会看是否是自己私有的,如果是:接下来就会私有处理,和外界毫无关系。如果不是私有的,则基于作用域链,去上级查找,直到找到全局作用域为止。


上述代码具体的执行过程,大家可以去看这一篇博客,我觉得做得非常详细。通过动图了解JS中的ECStack、EC、VO 和 AO - 知乎 (zhihu.com)


闭包



看一下Javascript高级程序设计对闭包的解释。闭包是指那些引用了另一函数作用域中的变量的函数,通常是在嵌套函数中实现的。


let x = 5;
const fn = function fn(x){
   return function(y){
       console.log(y + (++x));
   }
}
let f = fn(6)
f(7)
fn(8)(9)
f(10)
console.log(x);

接下来,我们一步步画图来理解这个给代码

先创建一个执行栈和堆,然后执行全局代码。执行全局代码时,会创建全局的执行上下文,基本数据类型会存在全局变量对象(VO)里面,函数创建会在堆内存中开辟一段新的空间,将地址赋给全局的变量。


let x = 5

会在VO中把x赋值为5


const fn = function fn(x){
    return function(y){
        console.log(y + (++x));
    }
}

然后会在堆内存中创建一个fn函数堆,初始化它的作用域为全局作用域,存储的代码为


return function(y){
    console.log(y + (++x));
}

然后fn的值是fn函数堆的地址


let f = fn(6)

先把fn执行,fn执行又会在堆内存开辟一段空间。然后再初始化作用域,因为我是向fn函数传入参数执行,即相当于向0x001(fn的地址)传参执行。初始化作用域是fn函数堆。如上文所说:上下文中的代码在执行的过程当中,如果上下文是函数,则其活动对象(activation object)用作变量对象。所以AO中x赋值为传入的参数6。重点来了,由于返回的是一个函数,所以又会开辟一个小函数堆,初始化作用域为fn1,存储的上下文代码是console.log(y + (++x));然后返回小函数的地址,所以f指向的是最后创建的小函数的地址。在全局对象中新添加f指向最后创建的小函数地址。

image.png

f(7)

相当于小函数堆0x100(7),在小函数堆的AO中创建变量y,赋值为传入的形参7。然后作用域中产找x,发现没有找到,则向上层作用域查找,上层作用域是0x001,查找到x = 6,++x时x的值变成了7,所以输出14


fn(8)(9)

先执行fn(8),又会重新创建一个堆内存,x的变量值为8,和上次一样,返回值是一个新函数的地址。再执行fn(8)(9),相当于将形参y赋值为9, y + (++x)执行,发现作用域中没有x,向上层查找,将x从8变成了9,然后9+9输出18


f(10)

++x时由之前的7变成了8,10 + 8输出18


console.log(x);

直接输出全局作用域的5


目录
相关文章
|
网络协议 程序员 编译器
C语言:编程世界的基础与魅力
C语言:编程世界的基础与魅力
|
存储 数据格式
|
云安全 弹性计算 负载均衡
阿里云ACP的认证值不值得考?考试题目难不难?
对于从事IT行业的人来说,想在这行做到长远的发展,就一定需要能体现自己价值的东西,能而最直观体现自己价值的东西,除了自己做过的项目,其他的就是自己持有的技能证书了。
377 0
阿里云ACP的认证值不值得考?考试题目难不难?
|
算法 API 开发工具
阿里云百炼平台综合评测
阿里云百炼作为一站式大模型开发平台,提供了从模型服务到应用开发的完整工具链。本文将基于实际搭建流程能力和模型训练的体验,对阿里云百炼平台进行详细评测。
686 3
|
存储 测试技术
LabVIEW编程开发PCB自动测试设备
LabVIEW编程开发PCB自动测试设备
140 1
|
10月前
|
Java 应用服务中间件 Maven
Maven的三种项目打包方式——pom,jar,war的区别
Maven 提供了多种打包方式,分别适用于不同类型的项目。pom 用于父项目或聚合项目,便于项目的结构和依赖管理;jar 用于Java类库或可执行的Java应用程序;war 则专用于Java Web应用程序的部署。理解这些打包方式的用途和特点,可以帮助开发者更好地配置和管理Maven项目,确保构建和部署过程的顺利进行。无论是单模块项目还是多模块项目,选择合适的打包方式对于项目的成功至关重要。
1401 3
|
机器学习/深度学习
斯坦福大学博士在GitHub发布的漫画机器学习小抄,竟斩获129k标星
斯坦福大学数据科学博士Chris Albon在GitHub上发布了一份超火的机器学习漫画小抄,发布仅仅一天就斩获GitHub榜首标星暴涨120k,小编有幸获得了一份并把它翻译成中文版本,今天给大家分享出来!
|
SQL 关系型数据库 MySQL
"Python与MySQL的浪漫邂逅:一键掌握增删改查,开启你的数据库编程之旅!"
【8月更文挑战第21天】Python因其简洁的语法和强大的库支持,成为连接数据库的首选工具。本文介绍如何使用Python连接MySQL数据库并执行基本操作。首先需安装`mysql-connector-python`库。通过配置连接信息建立数据库连接后,可利用`cursor.execute()`执行SQL语句进行数据的增删改查,并通过`commit()`提交更改。查询时使用`fetchall()`或`fetchone()`获取结果。记得处理异常及关闭连接以释放资源。掌握这些基础,有助于高效进行数据库编程。
165 0
|
Kubernetes 容器
Kubernetes(K8S) 配置静态资源服务
Kubernetes(K8S) 配置静态资源服务
168 0
|
安全 算法 网络安全
构建未来:量子计算在加密破解中的应用展望
随着量子信息科学的迅猛发展,量子计算技术已逐渐从理论走向实践。本文旨在探讨量子计算对传统加密算法的冲击及其在未来加密破解领域的应用潜力。通过分析量子计算的基本原理和目前的技术进展,我们着重讨论了量子计算机如何处理复杂的数学问题,以及它如何能够在理论上破解广泛使用的公钥加密体系。文章还提出了针对量子计算威胁下的加密策略调整建议,为未来的信息安全布局提供参考。
480 1