js执行上下文和执行上下文栈

80 阅读4分钟

知识前提:

1. 变量提升:

原理: JS引擎的工作方式是①先解析代码,获取所有被声明的变量;②然后在运行。也就是专业来说是分为预处理和执行两个阶段。

变量提升有两种:

1.var变量提升:通过var定义(声明)的变量,再定义语句之前就可以访问到.此时他的值为undefined

2.函数声明提升:通过function声明的函数,在声明之前就可以直接调用,值为函数定义(函数对象)

//函数声明式
function bar () {}
//函数字面量式 (这种只是把foo当作一个变量提升而不是函数提升)
var foo = function () {}

//因此下面这段代码运行会报错fn is not a function
fn(1);

var fn=function (a) {
    console.log(a)
}

当有多个同名变量声明的时候,函数声明会覆盖其他的声明。如果有多个函数声明,则是由最后的一个函数声明覆盖之前所有的声明。

代码示例如下

//函数提升优先级比变量提升要高,且不会被变量声明覆盖,但是会被变量赋值覆盖,也会被后声明的函数覆盖。
function a() {}
var a;
console.log(typeof a); //function


foo(); // 3  后面定义的函数覆盖了前面定义的函数
function foo() { 
  console.log( 1 ); 
}
var foo = function() { 
  console.log( 2 ); 
};
function foo() { 
  console.log( 3 ); 
}


var c = 1;

function c(c) {
  console.log(c);
}

c(2); //报错:c is not a function

2. 代码分类:
  • 全局代码
  • 函数(局部)代码
先看下面试题,大家猜一下输出结果是怎样呢? 试题涉及了变量提升和执行上下文栈,作用域和递归,大家可以回忆一下
<script type="text/javascript">
console.log('gb'+i);
var i =1
foo(1);
function foo(i){
    if(i==4){
        return;
    }
    console.log('fb'+i);
    foo(i+1);
    console.log('fd'+i);
}
console.log('ge'+i);
</script>

控制台打印输出是不是和预想的有些许不一样. 这不得不引出我们今天说的执行上下文了

JS代码在执行前,JS引擎总要做一番准备工作,这份工作其实就是创建对应的执行上下文;

执行上下文

1. 全局执行上下文
  • 在执行全局代码前将window确定为全局执行上下文

  • 对全局数据进行预处理

    var定义的全局变量设为undefined,添加为window的属性

    function声明的全局函数赋值(fn),添加为window的方法.

    this赋值window

  • 开始执行全局代码

2. 函数执行上下文
  • 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象

  • 对局部数据进行预处理

    形参变量赋值实参,添加为执行上下文的属性

    arguments赋值实参列表,添加为执行上下文的属性

    var定义的局部变量赋值为undefined,添加为执行上下文的属性

    function声明的函数赋值(fun),添加为执行上下文的方法

    this赋值调用函数的对象

  • 开始执行函数体代码

执行上下文栈

栈的特点:先进后出,后进先出.可以和队列的特点(先进先出)进行对比

  1. 在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后,将其添加到栈(压栈)
  3. 在函数执行上下文创建后将其添加到栈中(压栈),函数执行上下文只有函数被调用时才会创建,调用多少次函数就会创建多少上下文。
  4. 在当前函数不执行完后,将栈顶的对象移除(出栈)
  5. 当所有的代码执行完后,栈中只剩下window

根据执行上下文栈的特点,现在我们可以分析上面的试题

<script type="text/javascript">
//首先根据js自上而下执行顺序
先打印 gb:1
执行foo(1)
i为1打印 fb:1
调用foo(i+1)此时i为1.即执行foo(2)
打印 fb:2
调用foo(i+1)此时i为2.即执行foo(3)
打印 fb:3
调用foo(i+1)此时i为4.即执行foo(4)
直接return不打印
由于执行上下文栈的特点,后进先出,即后进入的先执行.因此先继续执行foo(3)
打印 fd:3
依次往栈底部继续执行
打印 fd:2
打印 fd:1
最后打印ge:1
因此打印顺序为gb:1 fb:1 fb:2 fb:3 fd:3 fd:2 fd:1 ge:1
console.log('gb:'+i);
var i =1
foo(1);
function foo(i){
    if(i==4){
        return;
    }
    console.log('fb:'+i);
    foo(i+1);
    console.log('fd:'+i);
}
console.log('ge:'+i);
</script>

试题图解如下:

执行上下文栈.png

还有一些相关试题,大家阔以根据相关知识看看输出结果

//试题一
function f1() {
    f2();
    console.log(1);
};

function f2() {
    f3();
    console.log(2);
};

function f3() {
    console.log(3);
};

f1();//3 2 1

//试题二
function foo(){
    var a = 1
    let b = 2
    {
      let b = 3
      var c = 4
      let d = 5
      console.log(a)
      console.log(b)
    }
    console.log(b) 
    console.log(c)
    console.log(d)
}   
foo()

欢迎大佬指教,刚开始写,求轻喷