当前位置: 首页 > 编程日记 > 正文

傻傻分不清的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引擎会创建新的执行环境并推到当前执行栈的顶端,在新的执行环境中,如果继续发生一个新函数调用时,则继续创建新的执行环境并推到当前执行栈的顶端,直到再无新函数调用。最上方的函数执行完成后,它的执行环境便从当前栈中弹出,并将控制权移交到当前执行栈的下一个执行环境,直到全局执行环境。当程序或浏览器关闭时,全局环境也将退出并销毁。

1513438-20181224134118896-1691306122.png

因此输出的结果为:

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中创建的作用域链如图:

1513438-20181224134138844-1122856326.png

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

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

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

五, 小结

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

参考资料:

  • https://stackoverflow.com/questions/20139050/what-really-is-a-declarative-environment-record-and-how-does-it-differ-from-an-a#
  • http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/
  • http://davidshariff.com/blog/javascript-scope-chain-and-closures/
  • https://github.com/yued-fe/y-translation/blob/master/en/understanding-execution-context-and-execution-stack-in-javascript.md

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

转载于:https://www.cnblogs.com/kidflash/p/10168153.html

相关文章:

SVN linux 服务器端配置

一. SVN 简单介绍Subversion(SVN) 是一个开源的版本号控制系統, 也就是说 Subversion 管理着随时间改变的数据。 这些数据放置在一个中央资料档案库 (repository) 中。 这个档案库非常像一个普通的文件server, 只是它会记住每一次文件的变动。 这样你就能够把档案恢复到旧的版本…

用C++开发Web应用

表现: XHTML/CSS/Javascript 库&#xff1a;Extjs 逻辑/服务器端&#xff1a;C CGI/Fastcgi 库&#xff1a;Wt Boost 数据库&#xff1a;MySQL/XML Web应用计划项目&#xff1a; Web DesktopSoft RobotWeb Instant MessageWeb Office SuitWeb IDE(compiler,…

C#2.0实例程序STEP BY STEP--实例二:数据类型

C#2.0实例程序STEP BY STEP&#xff0d;&#xff0d;实例二:数据类型 与其他.NET语言一样,C#支持Common Type Sysem(CTS),其中的数据类型集合不仅包含我们熟悉的基本类型,例如int,char和float等,还包括比较复杂的类型,例如内部的string类型和表示货币值的decimal类型。而且&am…

注解--python库--matplotlib

import matplotlib.pyplot as plt import numpy as npx np.linspace(-3, 3, 50) y 2*x 1plt.figure(num1, figsize(8, 5),) plt.plot(x, y,)ax plt.gca() ax.spines[right].set_color(none)#右边框为空 ax.spines[top].set_color(none)#上边框为空 ax.xaxis.set_ticks_posi…

Parallels Desktop 重装系统

安装教程&#xff0c;大家可以在网上找找 现在我想重装系统&#xff0c;怎么弄呢&#xff1f; 1、~/Documents/Parallels 目录下那个PVM后缀的文件直接删除 2、重装找开虚拟机&#xff0c;会弹出一个框&#xff0c;说找不到系统&#xff0c;点击删除。 3、之后重新安装即可。 转…

用tcpdump查看端口包

例如tcpdump -X -s1600 -i eth1 dst port 20072 -wdump.dat 特别注意-s的使用&#xff0c;不使用该参数&#xff0c;则只会接收默认大小的一个数据包&#xff0c;我机器上是42b 得到的数据包可以通过Ethereal软件查看详细包结构和内容。 很好用&#xff01; 转载于:https://ww…

Caught exception java.lang.interruptedException(在集群上进行多个文件合并压缩时出错)

问题&#xff1a;将mapreduce程序打成JAR包提交给yarn集群,用hadoop命令启动后发现报以下错误&#xff1a; 原因&#xff1a;经检查后发现少了这一行代码&#xff0c;此代码的作用是通过传入的class&#xff0c;找到job的JAR包。 解决方法&#xff1a;添上此行代码&#xff0c;…

是北京晚报!不,是中国最大的讽刺!!!

转载请注明出处:[url]http://technet.blog.51cto.com/[/url],这还是很久以前写的呢,今天无意翻出来了,发上来与大家共享.是北京晚报!不,是中国最大的讽刺!!!  昨天在北大青鸟上完课&#xff0c;坐车回家&#xff0c;因要&#xff12;个小时的车程&#xff0c;怕闲来无聊&…

nomn文件分析

#encodinggbk import os import re import math from os import path 手动输入文件nmon文件路径&#xff0c;要截取的开始时间&#xff0c;结束时间 rootdirE:\\pylianxi\\ceshi #input("请输入文件路径&#xff1a;") start_time"14:46" #截取的…

Vmware Workstation VMX 在资源管理器中杀不掉(虚拟机繁忙导致无法关机)

使用vmware的时候出现 虚拟机繁忙 的情况导致无法关机&#xff0c;然后使用任务管理器结束vmware进程&#xff0c;之后却发现在资源监视器中有一个VMware Workstation VMX进程始终关不掉&#xff0c;获得管理员权限去杀或者重启都没有用。 解决办法&#xff1a;打开win10的应用…

BGA封装芯片手工焊接攻略

转载于&#xff1a;http://blog.sina.com.cn/s/blog_70bb32080100lx1y.html 我毕设的很多板上都有BGA芯片&#xff0c;刚开始我觉得这东西实在是没有办法焊接。幸运的是我们研究所的另外一个研究室花了30多万买了个BGA焊接设备&#xff0c;我去蹭了2次&#xff0c;可惜要看人家…

40个常用的网站制作技巧

1. οncοntextmenu "window.event.returnValuefalse " 将彻底屏蔽鼠标右键 <table border οncοntextmenureturn(false)> <td> no </table> 可用于Table 2. <body onselectstart "return false "> 取消选取…

[Flash开发笔记] 如何在as2.0中使用自定义类事件

as2编程中&#xff0c;我们通常要处理一些异步加载的数据&#xff0c;有点类似ajax中的callback&#xff0c;即我们不知道何时数据才会返回&#xff0c;并且只有当数据返回时&#xff0c;执行我们定义的操作。在flash6及以前&#xff0c;我们会常常碰到从外部加载一张图片或一段…

.NET开发不可不知、不可不用的辅助类(三)(报表导出---终结版)

.NET导出报表一般是采用导出Excel报表的方式输出内容。而这又分为两种方式&#xff1a;使用Excel模板方式和使用网页输出Excel格式两种。首先介绍简单的一种&#xff0c;网页输出Excel内容&#xff0c;这种不需要引用Excel的程序集。/**//// <summary> /// 报表导出辅…

从应用到内核查接口超时(中)

应用复现 接着上文 从应用到内核查接口超时&#xff08;上&#xff09; 继续排查导致接口超时的原因。 转载随意&#xff0c;文章会持续修订&#xff0c;请注明来源地址&#xff1a;https://zhenbianshu.github.io 。 Jdk 的 native 方法当然不是终点&#xff0c;虽然发现 Jdk、…

OpenCV 之 Mat 类

数字图像可看作一个数值矩阵, 其中的每个元素代表一个像素点&#xff0c;如下图所示&#xff1a; OpenCV 中&#xff0c;用 Mat 来表示该数值矩阵&#xff0c;它是很关键的一种数据结构&#xff0c;因为 OpenCV 中的大部分函数都和 Mat 有关&#xff1a; 有的是 Mat 的成员函数…

hbase shell编码显示中文

最近测试hbase shell&#xff0c;碰到个中文显示编码问题&#xff0c;最后通过Python解决了问题&#xff0c;具体操作如下&#xff1a; hbase(main):015:0* scan ‘fr_test_hbase:test_log1’ ROW COLUMNCELL 10001 columninfo:name, timestamp1500448006065, valuetmr\xE4\xB…

AJAX范例大搜罗(转载)

1&#xff0e;每天一个AJAX 该网站提供了很多非常酷的AJAX例子&#xff0c;号称是每天更新一个。 网址&#xff1a;http://www.ajaxcompilation.com/ 2&#xff0e;210个AJAX框架 一个不错的提供Ajax范例的网站&#xff0c;Ajax框架已更新至210个。 网址&#xff1a;http:…

Hbase的过滤器查询

hbase过滤器的比较运算符&#xff1a; LESS < LESS_OR_EQUAL < EQUAL NOT_EQUAL <> GREATER_OR_EQUAL > GREATER > NO_OP 排除所有 hbase过滤器的比较运算符&#xff1a; BinaryComparator 按字节索引顺序比较指定字节数组&#xff0c;采用Bytes.compareTo(…

python的进程

多进程概念&#xff1a;   由于GIL的存在&#xff0c;python中的多线程其实并不是真正的多线程&#xff0c;如果想要充分地使用多核CPU的资源&#xff0c;在python中大部分情况需要使用多进程。python提供了非常好用的多线程包(multiprocessing)&#xff0c;只需要定义一个函…

071204 晴

晚上打算把周末的剩余任务做完去池袋kitty店预习作文把电脑慢的原因查出来电脑传照片的方法一部电影一本书一本杂志单词 转载于:https://www.cnblogs.com/loverain/archive/2007/12/04/982210.html

区块链深度好文

http://www.huhangfei.com/post/4/转载于:https://www.cnblogs.com/vinplezhang/p/7325161.html

工作流引擎设计之退回任务定义

退回&#xff08;Rollback Work Item&#xff09;退回是针对本人&#xff08;工作流参与者&#xff09;的“待办任务”的操作&#xff0c;即参与者主动退回待办任务列表中的任务。为什么要退回&#xff1f;参与者接受任务后&#xff0c;发现不应由自己办理此任务或上一步的执行…

HBase常用API操作

文章目录第一步&#xff1a;创建maven工程&#xff0c;导入jar包第二步&#xff1a;开发javaAPI操作HBase表数据1、创建表myuser2、向表中添加数据3、查询数据3.1、 按照rowkey进行查询获取所有列的所有值3.2、 按照rowkey查询指定列族下面的指定列的值3.3、 通过startRowKey和…

Kanade's trio 2017多校#3 trie

求数组中i<j<k 并且ai^aj<aj^ak的三元组组数 枚举插入ak&#xff0c;让ak中每一位作为最高位&#xff0c;查找字典树内最高位不同的数字数量 注意把ak的每个前缀做一个bad标记 存储让这个前缀作为i可以与字典树内形成i,j对的个数&#xff0c;这些不满足i<j ai : 1…

使用VS2005进行代码覆盖率分析

下面通过一个简单的例子来讲解VS2005是如何做代码分析的&#xff08;此处所做的代码分析是在单元测试之后进行的&#xff0c;其分析代码仍然使用上节的做和代码&#xff09; 1、上节的原始代码和单元测试代码分别如下&#xff1a; //原始代码 using System; using System.Colle…

云计算时代的数据库运行

云计算时代的高可用数据库是可扩展、容错且与任何私有云或公共云兼容的数据库实例。它们旨在提供业务连续性&#xff0c;而不会因任何类型的硬件或网络故障而导致用户体验的影响。其核心设计原则是消除任何单点故障&#xff0c;并提供平稳的故障转移体验。 公共云和私有云使企业…

Java:在Bean中使用PropertyChangeSupport支持PropertyChangeListeners

本文主要介绍如何使用PropertyChangeSupport类来支持关联属性事件的触发。author: ZJ 2007-8-3Blog: [url]http://zhangjunhd.blog.51cto.com/[/url]JavaBean的属性与一般Java程序中所指的属性&#xff0c;或者说与所有面向对象的程序设计语言中对象的属性是一个概念&#xff0…

【做题】SRM701 Div1 Hard - FibonacciStringSum——数学和式&矩阵快速幂

原文链接 https://www.cnblogs.com/cly-none/p/SRM701Div1C.html 题意&#xff1a;定义"Fibonacci string"为没有连续1的01串。现在&#xff0c;给出\(a,b\)&#xff0c;定义一个"Fibonacci string"的权值为\(x^a y^b\)&#xff0c;其中\(x\)为0的个数&…

scala定义抽象类与抽象字段

抽象类 和Java语言一样&#xff0c;scala中也可以定义抽象类 定义&#xff1a; 如果类的某个成员在当前类中的定义是不包含完整的&#xff0c;它就是一个抽象类 不完整定义有两种情况&#xff1a; 1.方法没有方法体&#xff08;抽象方法&#xff09; 2.变量没有初始化&#xf…