理解-Javascript执行上下文

基本概念

所谓的 JavaScript 执行上下文就是当前JS代码代码被解析和执行时所在环境的抽象概念,js代码都是在执行上下文中运行的

执行上下文类型

  • 全局执行上下文

    1. 它是最基础、默认的全局执行上下文
    2. 它会创建一个全局对象,并且将this指向全局对象,在浏览器中全局对象是window,在nodejs中全局对象是global
    3. 一个程序中只有一个
  • 函数执行上下文

    1. 有自己的执行上下文
    2. 可以在一个程序中存在任意数量
    3. 是函数被执行时创建
  • eval函数执行上下文
    eval函数可以计算某个字符串,并执行其中的js代码,这样就会存在一个安全性问题,在代码字符串未知或者是来自于用户输入源的话,绝对不要使用eval函数

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 全局执行上下文
var sayHello = 'Hello'
function someone() { // 函数执行上下文
var first = 'Tom', last = 'Ada'
function getFirstName() { // 函数执行上下文
return first
}
function getLastName() { // 函数执行上下文
return last
}
console.log(sayHello + getFirstName() + getLastName())
}
someone()

执行上下文的生命周期

执行上下文的生命周期分了三个阶段:

  • 创建阶段
    对于函数执行上下文,函数被调用的时候,但是还未执行里面的代码之前,会做三件事情:

    • 创建变量对象:会初始化函数的参数,提升函数声明和变量声明

    • 创建作用域链:作用域链用于标识符解析,看下面代码:
      f3函数被调用的时候,里面的变量num要求被解析的时候,会在当前f3的作用域里查找,如果没找到,就会向上一层作用域中查找,直到在全局作用找到该变量为30

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      var num = 30;
      function f1() {
      function f2() {
      function f3() {
      console.log(num);
      }
      f3();
      }
      f2();
      }
      f1();
    • 确定this指向:这个情况比较多,会在下文统一介绍
      在一个程序执行之前,要先解析代码,会先创建全局执行上下文环境,把需要执行的变量和函数声明都取出来并暂时赋值为undefined,函数也要先声明好待调用,这也是我们下文中会讲到的变量提升,以上几步做完后,开始正式执行程序

  • 执行阶段
    执行的变量赋值、函数调用等代码执行

  • 回收阶段
    执行上下文出栈,等待虚机垃圾回收执行上下文

变量提升

变量提升分为两种:

  • 变量声明提升
  • 函数声明提升

关于变量声明提升,先看以下代码片段:

1
2
3
4
5
6
7
console.log(a)  // undefined
var a = 5
function test() {
console.log(a) // undefined
var a = 10
}
test()

以上代码中,第1个 a 是在全局执行上下文环境中,由于在全局执行上下文创建的时候,把需要执行的变量和函数声明都取出来并暂时赋值为undefined,所以打印出来的就是undefined

第2个 a 是在test这个函数执行上下文环境中,同上,所以打印出来的就是undefined

1
2
3
4
5
6
7
8
9
var a
console.log(a) // undefined
a = 5
function test() {
var a
console.log(a) // undefined
a = 10
}
test()

打印结果在注释中,由于变量声明和函数声明提升原则可以把代码改成如下:

1
2
3
4
5
function f1() {}
console.log(f1) // function f1() {}
var f2;
console.log(f2) // undefined
f2 = function() {}

f1和f2不一样的地方是:f1是普通函数声明的方式,f2是函数表达式,在f2未被赋值的时候,它就是一个变量,这个时候变量提升,所以打印的f2为undefined

如果一个变量既是函数声明的方式,又是变量声明的方式,代码如下:

我们发现函数声明的优先级是高于变量提升的优先级的

1
2
3
4
5
6
7
8
9
function test(arg){
console.log(arg); // function arg(){console.log('hello world') }
var arg = 'hello';
function arg(){
console.log('hello world')
}
console.log(arg); // hello
}
test('hi');

总结:变量提升的几个特点:

如果有形参,先给形参赋值
函数声明的优先级是高于变量提升的优先级的,但可以重新赋值
私有作用域代码从上到下执行

执行上下文栈

js创建了执行上下文栈来管理执行上下文,我们通过如下一段代码和进栈出栈顺序图来理解执行上下文栈

1
2
3
4
5
6
7
8
9
10
var name = 'Tom';
function father() {
var sonName = 'Anda';
function son() {
console.log('son name is ', sonName)
}
console.log('father name is ', name)
son();
}
father();

过程:
1.全局执行上下文进栈
2.调用函数father,father函数执行上下文进栈
3.father函数内部代码执行,son函数被执行,son函数执行上下文进栈
4.son函数执行完毕,son函数的执行上下文出栈
5.father函数执行完毕,father函数的执行上下文出栈
6.浏览器关闭时,全局执行上下文出栈

执行上下文栈