傻傻分不清的javascript运行机制

学习到javascript的运行机制时,有几个概念经常出现在各种文章中且容易混淆。Execution Context(执行环境或执行上下文),Context Stack (执行栈),Variable Object(VO: 变量对象),Active Object(AO: 活动对象),LexicalEnvironment(词法环境),VariableEnvironment(变量环境)等,特别是 VO,AO以及LexicalEnvironment,VariableEnvironment的区别很多文章都没有涉及到。因此我查看了一些国内外的文章,结合自身理解写下了下面的笔记。虽然因为自身不足导致理解上的偏差,但是依然相信读完下文会对理解javascript的一些概念如变量提升,作用域和闭包有很大的帮助。

一, 执行环境和执行栈

了解javascript的运行机制,首先必须掌握两个基本的概念。Execution Context(执行环境或执行上下文)和Context Stack (执行栈)

1. 何为执行环境(执行上下文)(Execution Context)

我们知道javascript是单线程语言,也就是同一时间只能执行一个任务。当javascript解释器初始化代码后,默认会进入全局的执行环境,之后每调用一个函数,javascript解释器会创建一个新的执行环境。

    var a = 1;                       // 1.初始化默认进入全局执行环境function b() {                   // 3.进入b 的执行环境function c() {               // 5. 进入c的执行环境···}c()                          // 4.在b的执行环境里调用c, 创建c的执行环境}b()                              // 2. 调用b 创建 b 的执行环境
复制代码

执行环境的分类:

  • 全局执行环境:简单的理解,一个程序只有一个全局对象即window对象,全局对象所处的执行环境就是全局执行环境。
  • 函数执行环境:函数调用过程会创建函数的执行环境,因此每个程序可以有无数个函数执行环境。
  • Eval执行环境:eval代码特定的环境。

2. 如何单线程运行(Context Stack)

从一个简单的例子开始讲起

function foo(i) {if (i < 0) return;console.log('begin:' + i);foo(i - 1);console.log('end:' + i);
}
foo(2);
复制代码

如何存储代码运行时的执行环境(全局执行环境,函数执行环境)呢,答案是执行栈。而栈遵循的是先进后出的原理,javascript初始化完代码后,首先会创建全局执行环境并推入当前的执行栈,当调用一个函数时,javascript引擎会创建新的执行环境并推到当前执行栈的顶端,在新的执行环境中,如果继续发生一个新函数调用时,则继续创建新的执行环境并推到当前执行栈的顶端,直到再无新函数调用。最上方的函数执行完成后,它的执行环境便从当前栈中弹出,并将控制权移交到当前执行栈的下一个执行环境,直到全局执行环境。当程序或浏览器关闭时,全局环境也将退出并销毁。

因此输出的结果为:

begin:2
begin:1
begin:0
end:0
end:1
end:2
复制代码

3. 如何创建执行环境

我们现在知道每次调用函数时,javascript 引擎都会创建一个新的执行环境,而如何创建这一系列的执行环境呢,答案是执行器会分为两个阶段来完成, 分别是创建阶段和激活(执行)阶段。而即使步骤相同但是由于规范的不同,每个阶段执行的过程有很大的不同。

3.1 ES3 规范

创建阶段:

  • 1.创建作用域链。
  • 2.创建变量对象VO(包括参数,函数,变量)。
  • 3.确定this的值。

激活/执行阶段:

  • 完成变量分配,执行代码。
3.2 ES5 规范

创建阶段:

  • 1.确定 this 的值。
  • 2.创建词法环境(LexicalEnvironment)。
  • 3.创建变量环境(VariableEnvironment)。

激活/执行阶段:

  • 完成变量分配,执行代码。

我们从规范上可以知道,ES3和ES5在执行环境的创建阶段存在差异,当然他们都会在这个阶段确定this 的值 (关于this 的指向问题我们以后会在专门的文章中分析各种this 的指向问题,这里便不做深究)。我们将围绕这两个规范不同点展开。尽管ES3的一些规范已经被抛弃,但是掌握ES3 创建执行环境的过程依然有助于我们理解javascript深层次的概念。

二, Variable Object(VO: 变量对象),Active Object(AO: 活动对象)

2.1 基本概念

VO 和 AO 是ES3规范中的概念,我们知道在创建过程的第二个阶段会创建变量对象,也就是VO,它是用来存放执行环境中可被访问但是不能被 delete 的函数标识符,形参,变量声明等,这个对象在js环境下是不可访问的。而AO 和VO之间区别就是AO 是一个激活的VO,仅此而已。

  • 变量对象(Variable) object)是说JS的执行上下文中都有个对象用来存放执行上下文中可被访问但是不能被delete的函数标示符、形参、变量声明等。它们会被挂在这个对象上,对象的属性对应它们的名字对象属性的值对应它们的值但这个对象是规范上或者说是引擎实现上的不可在JS环境中访问到活动对象

  • 激活对象(Activation object)有了变量对象存每个上下文中的东西,但是它什么时候能被访问到呢?就是每进入一个执行上下文时,这个执行上下文儿中的变量对象就被激活,也就是该上下文中的函数标示符、形参、变量声明等就可以被访问到了

2.2 执行细节

如何创建VO对象可以大致分为四步

  • 1.创建arguments对象
  • 2.扫描上下文的函数声明(而非函数表达式),为发现的每一个函数,在变量对象上创建一个属性——确切的说是函数的名字——其有一个指向函数在内存中的引用。如果函数的名字已经存在,引用指针将被重写。
  • 3.扫描上下文的变量声明,为发现的每个变量声明,在变量对象上创建一个属性——就是变量的名字,并且将变量的值初始化为undefined。如果变量的名字已经在变量对象里存在,将不会进行任何操作并继续扫描。

注意: 整个过程可以大概描述成: 函数的形参=>函数声明=>变量声明, 其中在创建函数声明时,如果名字存在,则会被重写,在创建变量时,如果变量名存在,则忽略不会进行任何操作。

一个简单的例子

function foo(i) {var a = 'hello';var b = function privateB() {};function c() {}
}foo(22);
复制代码

执行的伪代码

// 创建阶段
fooExecutionContext = {scopeChain: { ... },variableObject: {arguments: {0: 22,length: 1},i: 22,c: pointer to function c()a: undefined,b: undefined},this: { ... }
}
// 激活阶段
fooExecutionContext = {scopeChain: { ... },variableObject: {arguments: {0: 22,length: 1},i: 22,c: pointer to function c()a: 'hello',b: pointer to function privateB()},this: { ... }
}
复制代码

三, LexicalEnvironment(词法环境),VariableEnvironment(变量环境)

3.1 基本概念

词法环境和变量环境是ES5以后提到的概念,官方对词法环境的解释如下。

词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符与特定变量和函数的关联关系。词法环境由环境记录(environment record)和可能为空引用(null)的外部词法环境组成。

简单的理解,词法环境是一个包含标识符变量映射的结构。(这里的标识符表示变量/函数的名称,变量是对实际对象【包括函数类型对象】或原始值的引用)。

ES3的VO,AO为什么可以被抛弃?个人认为有两个原因,第一个是在创建过程中所执行的创建作用域链和创建变量对象(VO)都可以在创建词法环境的过程中完成。第二个是针对es6中存储函数声明和变量(let 和 const)以及存储变量(var)的绑定,可以通过两个不同的过程(词法环境,变量环境)区分开来。

3.2 词法环境(lexicalEnvironment)

词法环境由两个部分组成

  • 环境记录(enviroment record),存储变量和函数声明
  • 对外部环境的引用(outer),可以通过它访问外部词法环境

对外部环境的引用关系到作用域链,之后再分析,我们先来看看环境记录的分类。

环境记录分两部分

  • 声明性环境记录(declarative environment records): 存储变量、函数和参数, 但是主要用于函数 、catch词法环境。 注意:函数环境下会存储arguments的值。而详细的过程可以参考VO 的执行细节,基本大同小异
  • 对象环境记录(object environment records), 主要用于with 和全局的词法环境

伪代码如下

// 全局环境
GlobalExectionContext = {  
// 词法环境LexicalEnvironment: {  EnvironmentRecord: {  ···}outer: <null>  }  
}
// 函数环境
FunctionExectionContext = {  
// 词法环境LexicalEnvironment: {  EnvironmentRecord: {  // 包含argument}outer: <Global or outer function environment reference>  }  
}
复制代码

3.3 变量环境(objectEnvironment)

变量环境也是个词法环境,主要的区别在于lexicalEnviroment用于存储函数声明和变量( let 和 const )绑定,而ObjectEnviroment仅用于存储变量( var )绑定。

3.4 伪代码展示

ES5规范下的整个创建过程可以参考下方的伪代码

let a = 20;  
const b = 30;  
var c;function d(e, f) {  var g = 20;  return e * f * g;  
}c = d(20, 30);
复制代码
// 全局环境
GlobalExectionContext = {this: <Global Object>,// 词法环境LexicalEnvironment: {  EnvironmentRecord: {  Type: "Object",  // 环境记录分类: 对象环境记录a: < uninitialized >,  // 未初始化b: < uninitialized >,  d: < func >  }  outer: <null>  },VariableEnvironment: {  EnvironmentRecord: {  Type: "Object",  // 环境记录分类: 对象环境记录c: undefined,  // undefined}  outer: <null>  }  
}
// 函数环境
FunctionExectionContext = {  this: <Global Object>,LexicalEnvironment: {  EnvironmentRecord: {  Type: "Declarative",  // 环境记录分类: 声明环境记录Arguments: {0: 20, 1: 30, length: 2},  // 函数环境下,环境记录比全局环境下的环境记录多了argument对象},  outer: <GlobalLexicalEnvironment>  },VariableEnvironment: {  EnvironmentRecord: {  Type: "Declarative",  // 环境记录分类: 声明环境记录g: undefined  },  outer: <GlobalLexicalEnvironment>  }  
}
复制代码

四,作用域链

前面讲创建过程中,我们留下了一个伏笔,ES3规范中有创建作用域链的过程,而ES5中在创建词法环境或变量环境的过程中,也有生成外部环境的引用的过程。那这个过程有什么作用呢。我们通过一个简单的例子来说明。

function one() {var a = 1;two();function two() {var b = 2;three();function three() {var c = 3;alert(a + b + c); // 6}}}one();
复制代码

当执行到three 的执行环境时,此时 a和b 都不在c 的变量内,因此作用域链则起到了引用外部执行环境变量的作用。ES3中创建的作用域链如图:

当解释器执行alert(a + b + c),他首先会找自身执行环境下是否有a这个变量的存在,如果不存在,则通过查看作用域链,判断a是否在上一个执行环境内部。它检查是否a存在于内部,若找不到,则沿着作用域链往上一个执行环境找,直到找到,或者到顶级的全局作用域。同理ES6规范中也可以这样分析。

因此这会引入一个javascript一个重要的概念,闭包。从上面对执行环境的解释我们可以这样理解,闭包就是内部环境通过作用域链访问到上层环境的变量。因此也存在无法进行变量回收的问题,只要函数的作用域链在,变量的值便因为闭包无法被回收。

注意: 此作用域链和原型链的作用域链不是同一个概念。

五, 小结

通过对javascript运行机制的介绍,对一些javasript高级概念有了更深的认识,特别是对一些云里雾里的概念区别有了更深刻的认识。不同规范下,不同概念的解释更有利于深挖javascript底层的执行思想。我相信这是理解javascipt语言最重要的一步。

参考资料:

  • stackoverflow.com/questions/2…
  • davidshariff.com/blog/what-i…
  • davidshariff.com/blog/javasc…
  • github.com/yued-fe/y-t…
  • segmentfault.com/a/119000000…

本文为博主原创文章,转载请注明出处 juejin.im/post/5c2052…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537275.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浙江省数字化改革回顾(2022年5月)

事业的伟大在于目标的壮丽&#xff0c;也在于过程的壮丽&#xff1b;改革的成果在于享有的丰富&#xff0c;也在于经历的丰富。2021年2月18日&#xff0c;春节假期后首个工作日&#xff0c;浙江省委召开全省数字化改革大会&#xff0c;在全国率先开启数字化改革探索实践。此后&…

python 某个数是不是在某个范围内_教写一个简单的python小程序(04)

点击蓝字关注我们 会酸的柚子Python爱好者搞机少年七夕结束了~酸柚也是被强塞了满嘴的狗粮在这样充满恋爱腐朽气息的一天酸柚也是马不停蹄的在赶稿子兄弟们&#xff0c;给我顶起来呀~我们来看看今日的题目可能很多小伙伴对完全平方数这个概念有点生疏了完全平方数数学上&#x…

Python:模块module

python中一个模块就是一个扩展名为.py的文件&#xff0c;也可能是预编译的.pyc文件。 引入模块用&#xff1a;import 模块名 使用引用模块中定义的标识符&#xff08;函数、变量、类&#xff09;用&#xff1a;模块名.标识符名 引入模块中的标识符用&#xff1a;from 模块名 im…

浙江公布2022年数字化改革“最系列“成果 评选出最佳应用104项

10月29日&#xff0c;省委改革办&#xff08;省数改办&#xff09;公布了2022年数字化改革“最系列”成果。该评选由省委改革办&#xff08;省数改办&#xff09;会同省委政研室、省人大常委会法工委、省市场监管局和省大数据局共同开展&#xff0c;评选了最佳应用104项、最强大…

dot net core 使用 IPC 进程通信

原文:dot net core 使用 IPC 进程通信版权声明&#xff1a;博客已迁移到 http://lindexi.gitee.io 欢迎访问。如果当前博客图片看不到&#xff0c;请到 http://lindexi.gitee.io 访问博客。本文地址 https://blog.csdn.net/lindexi_gd/article/details/79946496 dot net core 使…

python可变类型和不可变深浅拷贝类型_python3笔记十四:python可变与不可变数据类型+深浅拷贝...

一&#xff1a;学习内容python3中六种数据类型python赋值python浅拷贝python深拷贝二&#xff1a;python3六种数据类型1.六种数据类型Number(数字)string(字符串)List(列表)Tuple(元祖)Set(集合)Dictionary(字典)2.六种数据类型分类不可变数据(3个)&#xff1a;Number、String、…

Android手机用wifi连接adb调试的方法

https://www.jianshu.com/p/dc6898380e38 0x0 前言 Android开发肯定要连接pc的adb进行调试&#xff0c;传统的方法是用usb与pc进行连接&#xff0c;操作简单即插即用&#xff0c;缺点是pc上必须得有对应手机的usb驱动程序&#xff0c;对于谷歌亲儿子系列和三星摩托等外国品牌而…

控制台应用程序换换为窗体应用_Epic为开发者设计了一套iPhone使用的运动捕捉应用程序...

玩懂手机网7月13日资讯&#xff0c;我们都知道对于游戏或者是动漫开发者来说&#xff0c;运动捕捉设备是一套非常昂贵的设备&#xff0c;需要非常专业的独立开发人员&#xff0c;大量的时间才能完成&#xff0c;最近Epic为开发者设计了一套iPhone使用的运动捕捉应用程序。这套i…

蚂蚁金服亿级并发下的移动端到端网络接入架构解析

为了与金融从业者、科技从业者共同探讨金融 业务的深层次问题&#xff0c;蚂蚁金服联手 TGO 鲲鹏会上海分会&#xff0c;在 12 月 8 日举办了「走进蚂蚁金服&#xff1a;双十一背后的蚂蚁金服技术支持」活动。蚂蚁金服高级技术专家贾岛为大家分享了《亿级并发下的蚂蚁移动端到…

python3.12答案_编程常见问题

通常&#xff0c;不要使用 from modulename import * 。这样做会使导入器的命名空间变得混乱&#xff0c;并且使得连接器更难以检测未定义的名称。在文件的顶部导入模块。这样做可以清楚地了解代码所需的其他模块&#xff0c;并避免了模块名称是否在范围内的问题。每行导入一个…

如何根据视频的宽屏与竖屏来排序?

原理 宽屏与竖屏是根据 帧高度 与 帧宽度 来区分的 帧高度就是图片高度&#xff08;纵向的像素尺寸&#xff09;&#xff0c;帧宽度就是图片宽度&#xff08;横向的像素尺寸&#xff09;&#xff0c;分辨率就是&#xff08;高度x宽度&#xff09;。 windows11的文件排序&…

HashiCorp Vault 1.0开源自动解封特性,新增Batch令牌

HashiCorp发布了其秘密管理工具Vault 的1.0版本&#xff0c;并开源了在发生故障或重启后继续使用Vault服务器所需的“自动解封&#xff08;auto-unseal&#xff09;”特性。这个版本提供了一种可以用于临时工作负载的新令牌batch。另一个新特性是&#xff0c;Kubernetes auth现…

sap模块介绍_小迈说|SAP究竟有多少模块?

SAP究竟有哪些模块继上一期小迈说SAP&#xff01;SPA&#xff1f;的区别&#xff0c;相信大部分读者明白了我们与水浴按摩行业的分别&#xff0c;可是仅仅区分名字还不够&#xff0c;SAP还有众多的模块&#xff0c;这些又该怎么去了解呢&#xff1f;这就轮到肩负爱与责任的小迈…

360 再次开源管理平台 Wayne:基于企业级 Kubernetes 集群

2019独角兽企业重金招聘Python工程师标准>>> 奇虎 360 宣布正式开源 Wayne &#xff0c;这是一个由 360 搜索云平台团队开发的通用的、基于 Web 的 Kubernetes 多集群一站式可视化管理平台。内置了丰富多样的功能&#xff0c;满足企业的通用需求&#xff0c;同时插件…

python setup.py install 出错_python setup.py install 失败

python setup&period;py install 报错ImportError&colon; No module named setuptools学习光荣之路python课程时,使用python setup.py install安装其他模块时,第一次安装某模块成功了.安装另一模块却报错ImportError: No module named s ...对于python setup&perio…

Node.js 根本没有这样搞性能优化的?

1、使用最新版本的 Node.js 仅仅是简单的升级 Node.js 版本就可以轻松地获得性能提升&#xff0c;因为几乎任何新版本的 Node.js 都会比老版本性能更好&#xff0c;为什么&#xff1f; Node.js 每个版本的性能提升主要来自于两个方面&#xff1a; V8 的版本更新&#xff1b;Nod…

可交付成果、核实的可交付成果、验收的可交付成果?

①可交付成果。指的是在某一过程、阶段或项目完成时&#xff0c;产出的任何独特并可核实的产品、成果或服务。可交付成果可能是有形的&#xff0c;也可能是无形的。【研发完成】 ②核实的可交付成果。是指已经完成&#xff0c;并经过“控制质量”过程检查为正确的可交付成果。…

安装oracle到create inventory时卡住了怎么办_win10系统安装教程(官方工具)

Hi&#xff0c;大家好。对于小白用户&#xff0c;装系统是比较头疼的事&#xff0c;所以今天写一个简单易懂的装系统教程。使用微软官方提供的工具制作U盘启动盘&#xff0c;操作简单&#xff0c;系统纯净&#xff0c;强烈建议小白用户使用。缺点是该工具功能单一&#xff0c;并…

Microsoft Project 排计划的步骤

Microsoft Project 排计划的步骤&#xff1a; 第一步&#xff1a;设置项目信息&#xff0c;开始日期&#xff0c;选择日历&#xff1b; 第二步&#xff1a;编制WBS 第三步&#xff1a;设置前置任务 第四步&#xff1a;设置WBS每个工期 第五步&#xff1a;设置资源名称&#xff…