学习到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语言最重要的一步。

参考资料:

  • 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

傻傻分不清的javascript运行机制相关推荐

  1. 深入浅出JavaScript运行机制

    一.引子 本文介绍JavaScript运行机制,这一部分比较抽象,我们先从一道面试题入手: console.log(1); setTimeout(function(){ console.log(3); ...

  2. JavaScript 运行机制详解(理解同步、异步和事件循环)

    1.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. Java ...

  3. 我看朴灵评注阮一峰的《JavaScript 运行机制详解:再谈Event Loop》

    阮一峰和朴灵对我来说都是大牛,他们俩的书我都买过,阮老师的译作<软件随想录>和朴灵的<深入浅出node.js>.这个事情已经过了4个月了,所以我拿来讲应该也没啥问题. 这件事情 ...

  4. javascript运行机制

    阮一峰的网络日志 » 首页 » 档案 搜索 上一篇:乔布斯的管理课 下一篇:编译器的工作过程 分类: JavaScript JavaScript 运行机制详解:再谈Event Loop 作者: 阮一峰 ...

  5. javascript等待异步线程完成_作为前端你了解JavaScript运行机制吗?

    作为前端工程师,大家都知道js是前端一开始就要学会的知识点,js的代码你会写了,那js的运行机制你了解吗?只有了解了js的运行机制,才能在工作中如鱼得水,今天就跟随珠峰的老师一起来了解下js的运行机制 ...

  6. 【朴灵评注】JavaScript 运行机制详解:再谈Event Loop

    PS: 我先旁观下大师们的讨论,得多看书了~ 别人说的:"看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲 ...

  7. js中立即执行函数会预编译吗_作为前端你了解JavaScript运行机制吗?

    作为前端工程师,大家都知道js是前端一开始就要学会的知识点,js的代码你会写了,那js的运行机制你了解吗?只有了解了js的运行机制,才能在工作中如鱼得水,今天就跟随珠峰的老师一起来了解下js的运行机制 ...

  8. JavaScript运行机制:event-loop

    一.前言 JavaScript语言的特点是单线程,单线程只是指主线程,但不管是浏览器执行环境还是node执行环境,除了主线程还有其他的线程,如:网络线程,定时器触发线程,事件触发线程等等,这些线程是如 ...

  9. JavaScript 运行机制及原理

    写js也有两年多了,一直对它的运行机制和原理不是很了解,今天特意把大神们的理论和自己的总结都记录到下面: 什么是JavaScript解析引擎 简单地说,JavaScript解析引擎就是能够" ...

最新文章

  1. shell实现批量在多台windows服务器上执行同一命令并获取返回结果
  2. 事务中SET XACT_ABORT各种用法
  3. Ansible之playbook(剧本)介绍和配置详解
  4. webLogic11g部署war包问题解决方案(转)
  5. 基于开源蜜罐的实践与功能扩展
  6. Day12-流Stream
  7. 天燃气与电热水器哪个用着成本低?
  8. Hadoop MapReduce编程 API入门系列之wordcount版本3(七)
  9. Storm入门-Storm与Spark对比
  10. 面试常问点:深入剖析JVM的那些事
  11. python PyEnchant(拼写检查)
  12. 南京工资个税计算机,最新南京工资扣税标准
  13. 怎么找贷款意向客户?
  14. oracle数据库字符集子集与超集对应关系表表
  15. metaball公式_parametric.是什么意思
  16. 行业轮动策略(附策略源码)
  17. 黑马程序员--Objective-C之--block的使用以及协议的简单介绍
  18. java简单信息管理_用java写一个简单的学生信息管理系统
  19. vue vuex 模块化 namespace
  20. Dell笔记本Ins 5510:解决耳机插入笔记本无声音的问题

热门文章

  1. virtual hust 2013.6.20 数论基础题目 D - Just the Facts
  2. socket与TcpListener/TcpClient/UdpClient 的区别及联系
  3. cron 工具 每分钟_计划任务 cron和crontab
  4. usaco Sorting a Three-Valued Sequence 三值的排序
  5. java正则 group_Java正则表达式,分组 group()、groupCount()
  6. mysql 快速升级_快速升级MySQL系统表
  7. date数据类型的正确格式_说说数据类型 上篇日期
  8. C++:随笔3--复杂的数据结构
  9. PCL基础1:点云数据结构
  10. 力扣(LeetCode)刷题,简单题(第19期)