详释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 相关文章推荐
使用prototype.js进行异步操作
Feb 07 Javascript
javascript getElementsByClassName实现代码
Oct 11 Javascript
原生js实现shift/ctrl/alt按键的获取
Apr 08 Javascript
jQuery弹出(alert)select选择的值
Apr 21 Javascript
Jquery中children与find之间的区别详细解析
Nov 29 Javascript
JavaScript数组去重的3种方法和代码实例
Jul 01 Javascript
ES6学习笔记之Set和Map数据结构详解
Apr 07 Javascript
javascript函数的节流[throttle]与防抖[debounce]
Nov 15 Javascript
JS+HTML5实现获取手机验证码倒计时按钮
Aug 08 Javascript
vue中tab选项卡的实现思路
Nov 25 Javascript
vue 源码解析之虚拟Dom-render
Aug 26 Javascript
vue2.0 watch里面的 deep和immediate用法说明
Oct 30 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 session 检测和注销
2009/03/16 PHP
php命令行使用方法和命令行参数说明
2014/04/08 PHP
php json转换成数组形式代码分享
2014/11/10 PHP
PHP基于关联数组20行代码搞定约瑟夫问题示例
2017/11/07 PHP
php设计模式之享元模式分析【星际争霸游戏案例】
2020/03/23 PHP
为数据添加append,remove功能
2006/10/03 Javascript
jquery中的查找parents与closest方法之间的区别
2013/12/02 Javascript
setTimeout自动触发一个js的方法
2014/01/15 Javascript
jQuery多级弹出菜单插件ZoneMenu
2014/12/18 Javascript
Jquery实现仿腾讯娱乐频道焦点图(幻灯片)特效
2015/03/06 Javascript
jQuery实现选项卡切换效果简单演示
2015/12/09 Javascript
直接拿来用的页面跳转进度条JS实现
2016/01/06 Javascript
jQuery简单实现中间浮窗效果
2016/09/04 Javascript
JS实现间歇滚动的运动效果实例
2016/12/22 Javascript
Angularjs中使用layDate日期控件示例
2017/01/11 Javascript
JavaScript关联数组用法分析【概念、定义、遍历】
2017/03/15 Javascript
详解js location.href和window.open的几种用法和区别
2019/12/02 Javascript
解决vue单页面 回退页面 keeplive 缓存问题
2020/07/22 Javascript
vue 插槽简介及使用示例
2020/11/19 Vue.js
python实现定时自动备份文件到其他主机的实例代码
2018/02/23 Python
pandas 读取各种格式文件的方法
2018/06/22 Python
Django项目中添加ldap登陆认证功能的实现
2019/04/04 Python
python创建ArcGIS shape文件的实现
2019/12/06 Python
CSS3之边框多颜色Border-color属性使用示例
2013/10/11 HTML / CSS
美丽的珠宝配饰:SmallThings
2019/09/04 全球购物
总经理的岗位职责
2014/02/23 职场文书
走进敬老院活动总结
2014/07/10 职场文书
销售经理工作失职检讨书
2014/10/24 职场文书
精神病医院见习报告
2014/11/03 职场文书
中考学习决心书
2015/02/04 职场文书
认真学习保证书
2015/02/26 职场文书
2015年秋学期师德师风建设工作总结
2015/10/23 职场文书
亲情作文之母爱
2019/09/25 职场文书
MySQL命令行操作时的编码问题详解
2021/04/14 MySQL
从零开始在Centos7上部署SpringBoot项目
2022/04/07 Servers
最新动漫情报:2022年7月新番定档超过30部, OVERLORD骨王第四季也在其中噢
2022/05/04 日漫