JavaScript执行流程中的一些要点

182 阅读4分钟

JS执行流程

JS的执行流程分为两个阶段:编译阶段和执行阶段

image.png

编译阶段:形成执行上下文和可执行代码

执行阶段:执行代码,输出结果

执行上下文

执行上下文的定义

执行上下文是JavaScript执行一段代码时的运行环境。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。

执行上下文的分类

全局执行上下文--这是默认或者说基础的上下文
函数执行上下文--每当一个函数被调用时, 都会为该函数创建一个新的上下文
eval函数执行上下文--执行在 eval 函数内部的代码也会有它属于自己的执行上下文

创建执行上下文

  • this绑定
  • 创建词法环境组件
  • 创建变量环境组件 词法环境:分为环境记录器(Environment Record)和可能为null的外部引用(outer)ES6的定义
    变量环境:也是一个词法环境,在ES6中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(let 和 const)绑定,而后者只用来存储var变量绑定。
    变量提升:在创建阶段.JS引擎会对变量和函数进行提升,可以理解为将语句拆分成提升部分和执行部分。进行提升时,var声明的变量会初始化为undefined,而let和const不会初始化。
var mes = 'Hello World!'
function halo() {
  console.log(mes)
}
halo()
//提升部分
var mes  //声明
function halo() {
  console.log(mes)
}

//执行部分
mes = 'Hello World!'  //赋值
halo()

执行上下文栈(调用栈)

定义:执行上下文栈是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。当JavaScript引擎第一次遇到脚本的时候,会创建一个全局执行上下文并压入当前执行栈,每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部,引擎会执行那些执行上下文位于栈顶的函数。当函数执行完后,该函数的执行上下文弹出。引擎会执行那些执行上下文位于栈顶的函数。
注意:当入栈的执行上下文超过一定数目,JavaScript引擎会报错。
查看:谷歌开发者工具轴Source的Call Stack可以查看当前调用堆栈,也可通过console.trace()来输出当前函数的调用关系

例子

console.log('Hello World!');
function first() {
  console.log('first');
  second()
}

function second() {
  console.log('second');
}

function third() {
  console.log('third');
}

first();
third();
console.log('end');

image.png

作用域链

在 JavaScript 执行过程中,作用域链是由词法作用域决定的。
词法作用域:由函数声明的位置来决定,在编译阶段就已经决定好了,和函数怎么调用没有关系,所以词法作用域是静态作用域,而非动态作用域。

function bar() {
 console.log(myName)
}
function foo() {
  var myName = "foo"
  bar()
}
var myName = "window"
foo()

image.png

词法环境内部栈:词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出。
函数内的变量查找路径:词法环境(栈顶-->栈底)--> 变量环境 --> outer --> ... ...
块级作用域:var声明的变量会被存储到变量环境,let和const声明的变量被存储到词法环境,再结合词法环境的栈结构和变量的查找路径,就实现了块级作用域。

闭包

当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。


参考文章

浏览器工作原理与实践
理解JavaScript中的执行上下文和执行栈