详释JavaScript执行环境与执行栈

执行环境

执行环境 ( 也称"执行上下文" ) 可以说是 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 的运行时流程图如下:

(0)

相关推荐

  • javascript执行环境及作用域详解

    最近在重读<javascript高级程序设计3>,觉得应该写一些博客记录一下学习的一些知识,不然都忘光啦.今天要总结的是js执行环境和作用域. 首先来说一下执行环境 一.执行环境         书上概念,执行环境定义了变量或者函数有权访问的其他数据,决定了他们各自的行为.每个执行环境都有一个与之关联的变量对象.环境中定义的所有变量和函数都保存在这个对象中.虽然我们在编写代码的时候无法访问这个对象,但解析器在处理数据时会在后台用到它. 执行环境是一个概念,一种机制,它定义了变量或函数是否有权访

  • JavaScript执行环境及作用域链实例分析

    本文实例讲述了JavaScript执行环境及作用域链.分享给大家供大家参考,具体如下: 1.执行环境: 每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中. 执行环境包括全局执行环境和函数执行环境. 全局执行环境是最外围的一个执行环境,在浏览器中,全局执行环境被认为是是window对象,所有全局变量和属性都是作为window对象的属性和方法创建的. 函数执行环境是指函数的执行环境,当执行流进入一个函数时,函数的环境会被推入一个环境栈中,在函数执行之后,栈将其环境

  • 详释JavaScript执行环境与执行栈

    执行环境 执行环境 ( 也称"执行上下文" ) 可以说是 JavaScript 最重要的一个概念.那么执行环境到底是什么呢?一句话就可以概括:代码 ( 包括函数 ) 执行时所需要的所有信息就是执行环境.由于 ES 历经多个版本,所以执行环境的标准也一直在变,下面列出了三个主要的版本内容: ES3 标准中的执行环境 scope:作用域,如果有作用域嵌套的情况就称作"作用域链". variable object:变量对象,用于存储标识符的特殊对象. this value

  • 详解JavaScript执行模型

    JavaScript执行模型 引言 JavaScript是一个单线程(Single-threaded)异步(Asynchronous)非阻塞(Non-blocking)并发(Concurrent)语言,这些语言效果通过一个调用栈(Call Stack).一个事件循环(Event Loop).一个回调队列(Callback Queue)有些时候也叫任务队列(Task Queue)与跟运行环境相关的API组成. 概念 调用栈 Call Stack 调用栈是一个LIFO后进先出数据结构的函数运行栈,它

  • 浅谈JavaScript 执行环境、作用域及垃圾回收

    执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为.每个执行环境都有一个与之关联的变量对象. 全局执行环境是最外围的一个执行环境.根据JavaScript实现所在的宿主环境不同,表示执行环境的对象也不一样.在Web浏览器中,全局执行环境被认为是window对象.因此,所有的全局变量和函数都是作为window对象的属性和方法创建的. 变量对象:环境中定义的所有变量和函数都保存在这个对象中. 作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链.作用域链的用途是保证对执行环

  • JavaScript中的执行环境和作用域链

    前言 JS 中的执行环境和作用域链是非常重要的概念,它们是 JS 引擎在处理 JS 代码的时候对变量和函数的处理方式,这两个概念的正确理解能够帮助我们更好地理解和预测代码的行为. 执行环境 执行环境定义了变量或者函数有权访问的数据集合,每一个执行环境都有一个与之关联的变量对象,该执行环境中定义的所有变量和函数都保存在这个对象中.我们无法直接访问这个对象,这个对象只是在解析器处理数据的时候使用. 我们平时说的全局变量就是在最外围的一个执行环境中定义的变量,全局执行环境根据 ECMAScript 的

  • 深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解

    函数表达式 1.JavaScript中定义函数有2钟方法: 1-1.函数声明: 复制代码 代码如下: function funcName(arg1,arg2,arg3){  //函数体} ①name属性:可读取函数名.非标准,浏览器支持:FF.Chrome.safari.Opera. ②函数声明提升:指执行代码之前会先读取函数声明.即函数调用可置于函数声明之前. 1-2.函数表达式: 复制代码 代码如下: var funcName = function(arg1,arg2,arg3){  //函

  • javascript作用域链与执行环境详解

    前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 作用域.作用域链.执行环境.执行环境栈以及this的概念在javascript中非常重要,本人经常弄混淆,这里梳理一下: 局部作用域函数内部的区域,全局作用域就是window: 作用域链取决于函数被声明时的位置,解析标识符的时候就先找当前作用域,再向外查找,直到全局,这样一个顺序:和函数在哪里调用无关: 执行环境就是函数可访问的数据和变量的集合,也就是函数的作用域链上的所有数据和变量: 执行环境栈就是根

  • 详解JavaScript 的执行机制

    一.关于javascript javascript是一门单线程语言,在最新的HTML5中提出了Web Worker,但javascript是单线程这一核心仍未改变. 为什么js是单线程的语言?因为最初的js是用来在浏览器验证表单操纵DOM元素的.如果js是多线程的话,两个线程同时对一个DOM进行了相互冲突的操作,那么浏览器的解析是无法执行的. Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行.在主线程运行的同

  • 深入理解JavaScript 中的执行上下文和执行栈

    如果你是或者想成为一名 JavaScript 开发者,你必须知道 JavaScript 程序内部是如何执行的.理解执行上下文和执行栈对于理解其他 JavaScript 概念(如变量声明提升,作用域和闭包)至关重要. 正确理解执行上下文和执行栈的概念将使您成为更出色的 JavaScript 开发者. 闲话少说,让我们开始吧 :) 分享自 Bit的博客 使用 Bit 应用所提供的组件作为构建模块,你就是架构师.随时随地和你的团队分享.发现和开发组件,快来尝试鲜! Bit - 分享和创造代码组件: B

随机推荐