详释JavaScript执行环境与执行栈


Posted in Javascript onApril 02, 2019

执行环境

执行环境 ( 也称"执行上下文" ) 可以说是 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

示意图:

详释JavaScript执行环境与执行栈

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

详释JavaScript执行环境与执行栈

编译原理

我们知道,执行环境中有很多非常有用的" 工具 “,这些” 工具 “会协助引擎完成整个函数的执行工作。例如,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 的运行时流程图如下:

详释JavaScript执行环境与执行栈

Javascript 相关文章推荐
jquery学习总结(超级详细)
Sep 04 Javascript
js数组的操作指南
Dec 28 Javascript
14个有用的Jquery技巧分享
Jan 08 Javascript
javascript数组去重的六种方法汇总
Aug 16 Javascript
详解Weex基于Vue2.0开发模板搭建
Mar 20 Javascript
JS中将多个逗号替换为一个逗号的实现代码
Jun 23 Javascript
JQuery 获取多个select标签option的text内容(实例)
Sep 07 jQuery
JavaScript实现多叉树的递归遍历和非递归遍历算法操作示例
Feb 08 Javascript
vue之将echart封装为组件
Jun 02 Javascript
使用JS监听键盘按下事件(keydown event)
Nov 07 Javascript
vue+elementUI组件table实现前端分页功能
Nov 15 Javascript
微信小程序实现modal弹出框遮罩层组件(可带文本框)
Dec 20 Javascript
mongodb初始化并使用node.js实现mongodb操作封装方法
Apr 02 #Javascript
koa大型web项目中使用路由装饰器的方法示例
Apr 02 #Javascript
vue中v-text / v-html使用实例代码详解
Apr 02 #Javascript
Seajs源码详解分析
Apr 02 #Javascript
使用mixins实现elementUI表单全局验证的解决方法
Apr 02 #Javascript
移动端自适应flexible.js的使用方法(不用三大框架,仅写一个单html页面使用)推荐
Apr 02 #Javascript
基于vue实现web端超大数据量表格的卡顿解决
Apr 02 #Javascript
You might like
PHP开发文件系统实例讲解
2006/10/09 PHP
PHP教程 预定义变量
2009/10/23 PHP
php实现的一段简单概率相关代码
2016/05/30 PHP
ThinkPHP框架实现定时执行任务的两种方法分析
2018/09/04 PHP
基于Jquery的仿Windows Aero弹出窗(漂亮的关闭按钮)
2010/09/28 Javascript
jquery中.add()的使用分析
2013/04/26 Javascript
JS中的substring和substr函数的区别说明
2013/05/07 Javascript
JS中for循序中延迟加载动态效果的具体实现
2013/08/18 Javascript
各种页面定时跳转(倒计时跳转)代码总结
2013/10/24 Javascript
jquery 页面滚动到底部自动加载插件集合
2014/01/31 Javascript
基于 Docker 开发 NodeJS 应用
2014/07/30 NodeJs
原生js和jQuery随意改变div属性style的名称和值
2014/10/22 Javascript
解决js下referer兼容各大浏览器的方法
2014/11/03 Javascript
jquery动感漂浮导航菜单代码分享
2020/04/15 Javascript
浅析使用BootStrap TreeView插件实现灵活配置快递模板
2016/11/28 Javascript
ES6中Class类的静态方法实例小结
2017/10/28 Javascript
Angularjs之如何在跨域请求中传输Cookie的方法
2018/06/01 Javascript
微信小程序上传图片并等比列压缩到指定大小的实例代码
2019/10/24 Javascript
VSCode 添加自定义注释的方法(附带红色警戒经典注释风格)
2020/08/27 Javascript
Python学习小技巧之利用字典的默认行为
2017/05/20 Python
python调用Delphi写的Dll代码示例
2017/12/05 Python
python OpenCV学习笔记实现二维直方图
2018/02/08 Python
Python 绘制酷炫的三维图步骤详解
2019/07/12 Python
在Matplotlib图中插入LaTex公式实例
2020/04/17 Python
Python实现像awk一样分割字符串
2020/09/15 Python
Python识别验证码的实现示例
2020/09/30 Python
Numpy中np.max的用法及np.maximum区别
2020/11/27 Python
css3学习系列之移动属性详解
2017/07/04 HTML / CSS
浅析HTML5页面元素及属性
2021/01/20 HTML / CSS
英国领先的运动物理治疗供应公司:Vivomed
2018/07/14 全球购物
中国梦团日活动总结
2014/07/07 职场文书
股东授权委托书范本
2014/09/13 职场文书
2014流动人口计划生育工作总结
2014/12/20 职场文书
考教师资格证不要错过的4个最佳时机
2019/07/17 职场文书
MySQL创建索引需要了解的
2021/04/08 MySQL
Python图片处理之图片裁剪教程
2021/05/27 Python