执行上下文发挥着什么作用?
JavaScript 里面的代码应该是顺序执行的吧?(你也许会这样认为) 但一个很简单的例子就可以说明,JavaScript 代码在执行时实际上会发生一些微妙的变化:
num = 3;
var num;
console.log(num);
num 变量在声明之前就被赋值,居然没有报错???
这是因为 var num; 与 num = 3; 是属于代码运行的两个不同阶段的任务—— 编译阶段 与 执行阶段。 编译发生在执行之前,所以 var num; 先被执行,num = 3 便不被报错了。
声明语句(var num)最先被执行好比将声明语句 “移动” 至当前作用域的顶端,这个过程被称作提升。而提升与执行上下文的关系则十分密切(见后文)。
执行上下文的概念
当一段 JavaScript 代码在运行的时候,它实际上是运行在执行上下文中。而下列三种代码都会创建相应的执行上下文:
-
全局执行上下文:它是为运行代码主体而创建的执行上下文,也就是说它是为那些存在于函数之外的任何代码而创建的。
-
函数执行上下文:每个函数会在执行的时候创建自己的执行上下文。
执行上下文的创建 创建执行上下文有明确的几个步骤:1. 确定 this,即经常用到的 this 绑定。
- 创建 词法环境(LexicalEnvironment) 组件。
- 创建 变量环境组件(VariableEnvironment) 组件。
确定 this
在全局执行上下文中,this 总是指向全局对象。例如:浏览器环境下 this 指向 window 对象。
在函数执行上下文中,
this 的值取决于函数的调用方式,如果被一个对象调用,那么 this 指向这个对象。否则 this 一般指向全局对象 window 或者 undefined (严格模式)。
var name = "window";
let hello = {
name:"hello",
helloThis: function() {
console.log(this.name);
}
}
hello.helloThis(hello);
// window -> hello -> helloThis,由 hello 调用
let ht = hello.helloThis;
ht();
//window -> helloThis,由 window 调用
创建词法环境组件
词法环境是一个包含标识符变量映射的结构,同时,它还保存对父级词法环境的引用。 这里的标识符表示变量(函数)的名称,变量是对实际对象(包括函数类型对象)或原始值的引用。 如:var name = 1;。标识符是 name,引用是 1。 词法环境由环境记录器与对外部环境的引用两个组件组成。- 环境记录器:存储变量和函数声明的实际位置
- 外部环境的引用:实际上就是对外部或者说是父级词法环境的引用。这对理解闭包是如何工作的尤为重要。
-
词法环境就是在JavaScript 引擎创建一个执行上下文时,创建的用来存储变量和函数声明的环境,它可以使代码在执行期间,访问到存储在其内部的变量和函数,而在代码执行完毕之后,从内存中释放掉。(通过 var 定义的变量,存在于变量环境。)
创建变量环境
变量环境与词法环境十分相似。在 ES6 中,词法环境和变量环境的明显不同就是前者被用来存储函数声明和变量(let/const)的绑定,而后者只用来存储 var 变量的绑定。
执行上下文的执行
在执行上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。 值得注意的是,let 定义的变量(只有 let )如果未赋值,此阶段将赋值为 undefined。
执行上下文的管理(执行栈)
既然每个函数执行时都会产生相应的执行上下文。如果多的上下文该如何管理呢? 执行栈存储着所有执行上下文,并遵循着后进先出的原则:var say = function(){ hello(); } var hello = function(){ console.log("Hello,world!"); } say();1. 创建全局执行上下文,压入执行栈中。
- 调用了
say函数,创建say函数的执行上下文,并压入执行栈中。 - 进入
say函数内部,调用了hello函数,创建hello函数的执行上下文,并压入执行栈中。 hello函数执行完了,将hello移出执行栈say函数执行完了,将say移出执行栈