详释JavaScript执行环境与执行栈

所属分类: 网络编程 / JavaScript 阅读数: 1834
收藏 0 赞 0 分享

执行环境

执行环境 ( 也称"执行上下文" ) 可以说是 JavaScript 最重要的一个概念。那么执行环境到底是什么呢?一句话就可以概括:代码 ( 包括函数 ) 执行时所需要的所有信息就是执行环境。由于 ES 历经多个版本,所以执行环境的标准也一直在变,下面列出了三个主要的版本内容:

ES3 标准中的执行环境

  • scope:作用域,如果有作用域嵌套的情况就称作"作用域链"。
  • variable object:变量对象,用于存储标识符的特殊对象。
  • this value:this 值。

*标识符:包括变量、函数名、属性名和函数的参数。

ES5 标准中的执行环境

  • variable environment:变量环境,当声明变量时使用。
  • lexical environment:词法环境,当获取标识符值时使用。
  • this value:this 值。

ES6 标准中的执行环境

  • variable environment:变量环境,当声明变量时使用。
  • lexical environment:词法环境,当获取标识符值或者 this 值时使用。

*在 ES6 中,执行环境中实际增加了不少内容,我们这里只介绍了普通函数执行时所需要的内容。

执行栈

当打开网页或浏览器时,宿主环境(1)会将代码传递给引擎(2)去执行,引擎首先会创建一个全局执行环境。全局环境中的代码自上而下有顺序的执行,当遇到一个函数时,函数的环境被创建,函数中的代码开始执行;而在函数执行之后,控制权又返还给之前的环境。ES 这种类似于" 栈 "(3)的控制机制,称为执行栈。

(1) 宿主环境:浏览器或者 Node 环境。
(2) 引擎:从头到尾负责整个 JavaScript 代码的编译及执行过程。
(3) 栈:一种遵循" 后进先出 "原则的有序数据集合,可以简单理解为使用 push() 和 pop() 操作数组。

例子:

console.log(1);

function pFn() {
 console.log(2);
 (function cFn() {
 console.log(3);
 }());
 console.log(4);
}
pFn();

console.log(5);
//输出:1 2 3 4 5

示意图:

我们可以通过浏览器,直观的看一下执行栈的形式:

编译原理

我们知道,执行环境中有很多非常有用的" 工具 “,这些” 工具 “会协助引擎完成整个函数的执行工作。例如,ES3 标准中的作用域,它会协助引擎查找当前环境中所有标识符的定义的位置;变量对象,帮助引擎保存环境中的变量和函数。当然,这些工作大部分情况下发生在代码执行前的几微秒之内,称之为” 编译阶段 "。JavaScript 的整个编译阶段比较复杂,一般会经历词法分析、语法分析、代码生成、性能优化等步骤,这里不做深入讨论。

下面我们举例说明,看看当函数 fn 执行的时候,引擎是如何工作的:

var b=1;
function fn(){
 var a = 1;
 return a+b;
}
fn();

1、首先,遇到 var a,引擎会询问作用域是否已经有一个该名称的变量存在于同一个作用域中。如果存在,引擎会忽略该声明,继续进行编译;很显然不存在,所以引擎会在当前作用域中声明一个新的变量,并命名为 a ( 此时还没有赋值,默认为 undefined )。

2、第二步,又遇到 a,引擎会首先询问作用域,在当前的作用域中是否存在一个叫作 a 的变量,很显然存在,所以引擎就会使用这个变量;遇到 b,引擎对作用域做出同样的询问,很显然不存在,所以引擎会到外层嵌套的作用域中继续查找,在全局作用域找到了该变量,引擎就会将 1 赋值给变量 b 。

3、经过以上两步,函数 fn 环境中出现的所有标识符的值已经基本锁定,那么引擎就会立即自上而下开始执行代码。为变量 a 赋值 1,计算 1+1 的值并返回它。

4、最后一步,函数 fn 的环境销毁,退出执行栈,将控制权返还给全局环境。

变量提升的原因

在编译阶段,引擎会声明变量和函数,但不会对变量进行赋值,这主要是出于对性能的考虑。变量被声明,但是不一定会在后面使用到,如果没有使用却赋了值,只是白白浪费内存而已。上面例子中的全局变量 b ,在函数 fn 没有执行之前,也不会赋值,直到函数中使用了这个变量,才不得不去加载数字 1。简单的说,var a 这段代码发生在编译阶段,而 =1 这段代码会根据实际情况,发生在执行阶段,这也就是" 变量提升 "的原因。另外需要注意的是,函数声明的是整个函数体( 因为函数声明不存在赋值操作),而且优先级高于同名的变量。

例子1:

console.log(fn()); //输出:1
console.log(n); //输出:undefined

function fn() {
 return 1;
}
var n = 2;

由于声明发生在赋值的前面,上面例子1的代码可以理解为下面的形式:

function fn() {
 return 1;
}
var n;

console.log(fn()); //输出:1
console.log(n); //输出:undefined

n = 2;

由于函数声明优先级高,因此同名变量声明会被忽略,上面例子2的代码可以理解为下面的形式:

function fn() {
 console.log(1);
}

//由于函数声明优先级高,因此这个变量声明会被忽略
//var fn;

fn(); //输出:1

fn = function() {
 console.log(2);
}

*变量提升并非物理意义上的顺序改变,代码执行的顺序还是按照你书写代码时的顺序在执行。只是由于,变量声明发生在代码的编译阶段,而变量赋值却发生在代码的执行阶段,时间上的差异导致了这种现象。

运行时流程图

综合以上的内容,JavaScript 的运行时流程图如下:

更多精彩内容其他人还在看

jQuery控制input只能输入数字和两位小数的方法

这篇文章主要介绍了jQuery控制input只能输入数字和两位小数的相关知识,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Vue模板语法中数据绑定的实例代码

这篇文章主要介绍了Vue模板语法中数据绑定的实例代码,非常不错,具有一定的参考借鉴价值 ,需要的朋友可以参考下
收藏 0 赞 0 分享

详解 微信小程序开发框架(MINA)

小程序使用的是MINA框架,目的是通过简单、高效的方式让开发者可以在微信中开发具有原生App体验的服务。 这篇文章主要介绍了微信小程序开发框架(MINA),需要的朋友可以参考下
收藏 0 赞 0 分享

jQuery实现的点击显示隐藏下拉菜单功能完整示例

这篇文章主要介绍了jQuery实现的点击显示隐藏下拉菜单功能,结合完整实例形式分析了jQuery事件响应及页面元素属性动态操作简单实现技巧,需要的朋友可以参考下
收藏 0 赞 0 分享

angular4应用中输入的最小值和最大值的方法

这篇文章主要介绍了angular4应用中输入的最小值和最大值的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

150行代码带你实现微信小程序中的数据侦听

在这篇文章中, 我将用150行代码, 手把手带你打造一个小程序也可以使用的侦听器,感兴趣的朋友跟随小编一起看看吧
收藏 0 赞 0 分享

javascript异步编程的六种方式总结

这篇文章主要介绍了javascript异步编程的六种方式总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

JS实现的自定义map方法示例

这篇文章主要介绍了JS实现的自定义map方法,结合实例形式分析了javascript自定义map相关的json数组定义、遍历、添加、删除、读取等相关操作技巧,需要的朋友可以参考下
收藏 0 赞 0 分享

微信小程序云开发(数据库)详解

使用云开发开发微信小程序、小游戏,无需搭建服务器,这篇文章主要为大家详细介绍了微信小程序云开发数据库,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

JS简单数组排序操作示例【sort方法】

这篇文章主要介绍了JS简单数组排序操作,结合实例形式分析了javascript使用sort方法进行数组排序的相关操作技巧,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多