参考JStamping的执行上下文和词法环境面试官:说说执行上下文javascript图JavaScript引擎分享一个可视化的JS引擎执行流程深入理解JavaScript执行流程(其中JS之一系列)前端开发的JavaScript原理:引擎基础js解析器(引擎)执行过程编译阶段的JS引擎有很多种,比如v8(chrome)、JSCore(safari)等,就拿V8引擎based以Node.js和Chrome浏览器为例:HTML解析器遇到脚本标签并从网络或缓存中请求该脚本。脚本响应是作为字节流的脚本。它的解码字节流decoder从解码后的字节流中创建词法单元token【词法分析阶段】token可以理解为语法上不能进一步划分的最小单个字符或字符串。例如,0066解码为f,0075解码为u,006e解码为n,0063解码为c,0074解码为t,0069解码为i,006f解码为o,006e解码为n后跟一个空格。这是关键字函数,为此创建了一个tokenvara=2,这个程序将被分解为:“var,a,=,2,;”五个tokentoken列表被发送到解析器(parser)并进行预解析。预解析器处理后面可能用到的代码,而解析器处理立即需要的代码。例如,如果某个函数仅在用户单击按钮后调用,则无需立即编译代码来加载网站。.如果用户最终单击按钮并需要此代码,则会将其发送到解析器。解析器从字节流解码器接收令牌,创建节点,并形成抽象语法树(AST)。【语法分析阶段】AST用树形结构来表达源代码的语法结构。解释器(interpreter)遍历AST,生成字节码,最后删除AST字节码。它是介于AST和机器码之间的代码。只有转换成机器码后,字节码才能被执行,送往优化编译器(optimizingcompiler),生成高度优化的本地机器码【JIT即时编译技术,只有部分js引擎有;优化编译器也叫JIT编译器】将AST转换为可执行代码的过程称为代码生成,因为计算机只能识别机器指令总结:词法分析:形成词法单元token【字节流解码器】语法分析:将token列表转化为抽象语法树AST[Parser]代码生成:将AST转化为字节码[解释器],机器可以识别的机器码[优化编译器]上面是编译阶段,下面是执行阶段。在执行阶段之前,需要做一个准备工作,即生成执行上下文(创建阶段)。执行上下文概念执行上下文(简称EC)是代码运行的环境。每当JS运行时,它都在执行上下文中运行。主要记录代码执行过程中的状态信息,如:变量对象定义作用域链的扩展提供了调用者的对象引用等信息,当代码结束时,执行上下文也会被销毁。ExecutionContextStack&EventLoopExecutionMechanism执行栈(callstack)是用来存放不同执行上下文的栈结构(后进先出),因为脚本运行时可能会调用很多函数,导致很多函数执行上下文,统一交给执行栈管理。执行栈的容量是有限的。如果积压达到一定程度并继续累积,就会报“堆栈溢出”(stackoverflow)错误。栈溢出错误经常出现在递归(调用自身)中。EventLoop事件循环是JS的执行机制。由于js引擎线程是单线程的,为了合理安排同步任务和异步任务的执行,定义了一个事件循环机制来协调。EventLoop会先执行执行栈中的任务,然后从事件队列中读取事件,添加到执行栈中执行,等等。执行上下文分类根据不同的执行场景,执行上下文分为:全局执行上下文是从js代码开始的默认执行环境,在js脚本的整个生命周期中都存在于执行栈的底部,会不会被栈弹出破坏。全局上下文会在生成一个全局对象(在浏览器中为window,在node中为global)并将this指向全局对象当函数执行上下文函数调用时,无论函数被调用多少次,都会生成一个新的上下文每次执行eval执行上下文时生成。它将有自己的执行上下文。执行上下文生命周期创建阶段初始化变量对象如果是函数执行上下文,它会用参数列表(arguments)初始化变量对象,并在变量对象中添加函数内部的变量声明和函数声明。这个阶段对变量和函数进行初始化和声明,变量定义为undefined,直接定义函数。即变量提升,变量和函数都会提升,但函数会更高。//变量提升时,函数更高级,所以会被同名变量覆盖语句执行,沿着作用域链访问变量,为之前声明的变量赋值,创建新的函数执行上下文和遇到函数调用时压入栈,交接控制权。在销毁阶段,一般情况下,函数执行完成后,会将当前上下文从执行栈中弹出进行销毁,将控制权交还给上层的执行上下文。但是闭包的情况就不同了。当包裹闭包函数的父函数执行时,父函数本身执行环境的作用域链被破坏,但是由于闭包函数有对父函数变量的引用,所以父函数的变量对象一直存在于内存并且不能被访问Destroyed,除非闭包的引用被删除。过度使用闭包导致未使用的内存,没有及时释放,可能会出现“内存泄漏”。ES3执行上下文的内容在ES3的定义中,执行上下文包括:变量对象(variableobject(VO)简称VO)激活对象(activationobject(AO)简称activationobject(AO))作用域链(scopechain)调用者信息(this)全局执行上下文中的变量对象中,变量对象是全局对象,在浏览器的情况下是window。var定义的全局变量或函数会成为window的属性和方法,但let和const的顶级声明不会。在函数执行上下文中,变量对象会包含函数的参数列表(arguments),以及函数内部声明的变量和函数(注意函数声明会加在变量对象中,函数表达式将不会)。但是,它不能直接访问。当函数被调用时,VO变成AO。//函数声明functiona={}//函数表达式//b是变量声明,会被加到变量对象中//foo是函数表达式,会被忽略letb=functionfoo(){}当调用活动对象函数,将函数上下文的变量对象转化为活动对象。其实可变对象和活动对象是一回事,只是处于不同的状态和阶段。作用域链Scope规定了如何查找变量,即当前执行的代码对变量的访问权限。创建执行上下文时,会为变量对象创建一个作用域链:在查找变量时,如果当前上下文中不存在该变量对象,则会在上层上下文中的变量对象中查找,直到到达全球背景。这种由多级上下文的变量对象组成的链表称为作用域链。函数创建时,作用域已经确定:创建作用域内部属性,保存所有父变量对象;函数执行时创建执行上下文,复制作用域构建作用域链,然后将VO转换为AO添加到函数域链前端。Callerinformation这是创建执行上下文时创建的属性,绑定到这个的对象取决于函数调用的条件。直接使用一个没有任何修饰的函数引用来调用,然后绑定到window【默认规则】如果调用位置有上下文对象,则this绑定到那个上下文对象【隐式绑定】使用call,apply,bind显式绑定[Explicitbinding]使用new运算符将this绑定到一个新对象上[newbinding]箭头函数的this不应用上述规则,而是根据外层作用域进行绑定:它将等于this外层函数或者全局对象数据结构模拟如果执行上下文用代码表示:executionContext:{[variableobject|activationobject]:{arguments,variables:[...],functions:[...]},scopechain:variableobject+allparentsscopesthisValue:contextobject}ES5执行上下文内容ES5调整了执行上下文的概念通过删除变量对象和活动对象并用LexicalEnvironment组件和VariableEnvironment组件替换它们。所以ES5的执行上下文的概念表示大致如下:ExecutionContext={ThisBinding=
