JavaScript | 2019-09-12 11:42:02 1469次 9次
分词 --> 解析(AST)-->(Ignition解释器)字节码 --> 逐条解释执行(热点代码则通过TurboFan编译为机器码)(还有情况将热点回退到字节码解释执行,比如函数参数的类型发生改变)。
简而言之,执行上下文就是当前 JavaScript 代码被解析和执行时所在环境的抽象概念, JavaScript 中运行任何的代码都是在执行上下文中运行。
执行上下文总共有三种类型:
全局执行上下文: 这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将 this
指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
全局执行上下文环境是在全局作用域确定之后。
一个作用域在一个时刻可以拥有多个执行上下文,当处于活动状态的只有一个
函数执行上下文: 每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤,具体过程将在本文后面讨论。
Eval 函数执行上下文: 运行在 eval
函数中的代码也获得了自己的执行上下文。
在函数创建的时候创建一个包含全局变量对象的作用域链,储存在内部[[Scope]]属性中。函数执行的时候会创建一个执行环境,通过复制[[Scope]]属性中的对象,构建执行环境的作用域链,并把自己的活动对象推入该作用域链的前端以此形成完整的作用域链。[[Scope]]保存的是对全局变量的引用,而不是值的复制。
上下文生命周期:
1、创建环节(函数被调用,但未未被执行)会执行三件事情
创建变量对象,首先初始化函数的arguments对象,提升函数声明和变量声明,从近到远查找函数运行所需要的变量。
创建作用域链,作用域就是一个独立的地盘,让变量不会相互干扰,当前作用域没有定义的变量,这成为 自由变量。自由变量会向上一直寻找,要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,如果最终没有就为undefined。这种层层之间就构成了作用域链。
确定this指向,this、apply、call的指向
2、执行阶段:
变量赋值
函数引用
执行其他代码
3、销毁阶段:执行完毕出栈,等待回收被销毁
执行栈,在其他编程语言中也被叫做调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
当 JavaScript 引擎首次读取你的脚本时,它会创建一个全局执行上下文并将其推入当前的执行栈。每当发生一个函数调用,引擎都会为该函数创建一个新的执行上下文并将其推到当前执行栈的顶端。
引擎会运行执行上下文在执行栈顶端的函数,当此函数运行完成后,其对应的执行上下文将会从执行栈中弹出,上下文控制权将移到当前执行栈的下一个执行上下文。
补充:JavaScript 开始要解析执行代码的时候,最先遇到的就是全局代码,所以 JavaScript 引擎会先解析创建全局执行上下文,然后将全局执行上下文压栈。然后当执行流进入一个函数时,会先解析创建函数执行上下文,然后将它的执行上下文压栈。而在函数执行之后,会将其执行上下文弹栈,弹栈后执行上下文中所有的数据都会被销毁,然后把控制权返回给之前的执行上下文。注意,全局执行上下文会一直留在栈底,直到整个应用结束。
作用域&执行上下文的区别与联系:
区别:
1、什么时间确定?
作用域:
全局作用域&局部作用域都是在代码编写时确定的。
执行上下文环境:
全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
2、状态
作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放,作用域链也解散
联系:
1. 上下文环境(对象)是从属于所在的作用域
2. 全局上下文环境==>全局作用域
3. 函数上下文环境==>对应的函数作用域
闭包产生的关键原因:
var fruit = 'banana'; var inner; (function() { var fruit = 'apple'; inner = function() {console.log(fruit);} })(); console.log(fruit); // 'banana' inner(); // 'apple'
在全局作用域中我们声明了一个名为 inner 的变量,在自执行函数中我们把一个 log 出 fruit 变量值的函数作为值赋给全局变量 inner。正常情况下,当自执行函数结束后,其内部的局部变量 fruit 应该被销毁,就像我们前面 2 个例子那样。但是因为在 inner 函数中依然保持着对局部变量 fruit 的引用,所以最后我们在调用 inner 时会 log 出 apple。这时可以说我们创建了一个闭包。
一个闭包会在这种情况下被创建:一个内层函数嵌套在一个外层函数里,这个内层函数被储存在其外层函数作用域之外的作用域的 variable 对象中,同时还保存着对其外层函数局部变量的引用。虽然外层函数中的这个 inner 函数不会再被运行,但其对外层函数变量的引用却依然保留着,这是因为在函数内部的作用域链中依然保存着该变量的引用,即使外层的函数此时已经不存在了。
要记住一个函数的作用域链同它的执行上下文是绑定的,同其他那些与执行上下文关联紧密的对象一样,作用域链在函数执行上下文被创建之后创建,并随着函数执行上下文的销毁而销毁。解析器只有在函数被调用时才会创建该函数的执行上下文。在上面的例子中,inner 函数是在最后一行代码被执行时调用的,而此时,原匿名函数的执行上下文(连同它的作用域链和 variable 对象)都已经被销毁了。那么 inner 函数是如何引用到已经被销毁的保存在局部作用域中的局部变量的呢?
这个问题的答案引出了函数内部对象中一个被称为 scope 属性(scope property)的对象。所有的 JavaScript 函数都有其自身的内在 scope 属性,该对象中储存着用来创建该函数作用域链的那些对象。当解析器要为一个函数创建作用域链,它会去查看 scope 属性看看哪些项是需要被加进作用域链中的。因为相比执行上下文,scope 属性同函数本身的联系更为紧密,所以在函数被彻底销毁之前,它都会一直存在——这样苦于保证不了函数被调用多少次,它都是可用的。
一个在全局作用域中被创建的函数拥有一个包含了 global 对象的 scope 对象,所以它的作用域链仅包含了 global 对象和和它自己的 variable 对象。一个创建在其他函数中的函数,它的 scope 对象包含了封装它的那个函数的 scope 对象中的所有对象和它自己的 variable 对象。
浏览器事件循环:
9人赞