执行上下文和执行栈属于js引擎的执行过程的预编译阶段。前端
执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。能够理解为当执行代码时作的准备工做。git
执行上下文按照运行环境被分红三类:github
全局执行上下文(JS代码加载完毕后,进入代码预编译即进入全局环境)浏览器
函数环境执行上下文(函数调用执行时,进入该函数环境,不一样的函数则函数环境不一样)安全
eval执行上下文(不建议使用,会有安全,性能等问题)ide
对于每一个执行上下文,都有三个重要属性:函数
VO:变量对象(Variable object);post
AO:活动对象(activation object),当进入函数执行上下文时,这个函数执行上下文的变量对象才会被激活,因此才叫 activation object,而只有被激活的变量对象,也就是活动对象上的各类属性才能被访问(进入函数上下文时建立活动对象)。性能
执行栈,也叫调用栈,具备 LIFO(后进先出)结构,用于存储在代码执行期间建立的全部执行上下文。this
首次运行JS代码时,会建立一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数建立一个新的函数执行上下文并Push到当前执行栈的栈顶。
根据执行栈LIFO规则,当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文。
全局执行上下文在浏览器关闭或者标签关闭时才会出栈。
1 var a = 'Hello World!'; 2 3 function first() { 4 console.log('Inside first function'); 5 second(); 6 console.log('Again inside first function'); 7 } 8 9 function second() { 10 console.log('Inside second function'); 11 } 12 13 first(); 14 console.log('Inside Global Execution Context'); 15 16 // Inside first function 17 // Inside second function 18 // Again inside first function 19 // Inside Global Execution Context
建立阶段就是肯定三个重要上下文属性的过程。
建立arguments对象,检查当前上下文中的参数,创建该对象的属性与属性值,仅在函数环境(非箭头函数)中进行,全局环境没有此过程
检查当前上下文的函数声明,按代码顺序查找,将找到的函数提早声明,若是当前上下文的变量对象没有该函数名属性,则在该变量对象以函数名创建一个属性,属性值则为指向该函数所在堆内存地址的引用,若是存在,则会被新的引用覆盖。
检查当前上下文的变量声明,按代码顺序查找,将找到的变量提早声明,若是当前上下文的变量对象没有该变量名属性,则在该变量对象以变量名创建一个属性,属性值为undefined;若是存在,则忽略该变量声明
变量提高的缘由:
在建立阶段,函数声明存储在环境中,而变量会被设置为 undefined
(在 var
的状况下)或保持未初始化(在 let
和 const
的状况下)。因此这就是为何能够在声明以前访问 var
定义的变量(尽管是 undefined
),但若是在声明以前访问 let
和 const
定义的变量就会提示引用错误的缘由。这就是所谓的变量提高。
从建立顺序看,函数提高优先于变量提高。
由多个执行上下文的变量对象构成的链表就叫作做用域链。
当函数建立的时候,函数有一个内部属性会保存全部父变量对象到其中。
进入函数上下文,建立 VO/AO 后,就会将活动对象添加到做用链的前端。
因此:
做用域链的第一项永远是当前做用域(当前上下文的变量对象或活动对象);
最后一项永远是全局做用域(全局执行上下文的活动对象);
做用域链保证了变量和函数的有序访问,查找方式是沿着做用域链从左至右查找变量或函数,找到则会中止查找,找不到则一直查找到全局做用域,再找不到则会抛出引用错误。
全局执行上下文中变量对象的this属性指向为window,函数上下文则较为复杂,之后会详细介绍。
完成对全部变量的分配,最后执行代码。
参考文档: