【JavaScript】JS执行上下文Context


执行上下文

执行上下文(Execution Context): 函数执行前进行的准备工作(也称执行上下文环境)

运行JavaScript代码时,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作,如确定作用域,创建局部变量对象等。

简而言之,执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。

JavaScript中执行环境

  • 全局环境
  • 函数环境
  • eval函数环境 (已不推荐使用)

那么与之对应的执行上下文类型同样有3种JavaScript执行上下文类型

  • 全局执行上下文:这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 函数执行上下文 : 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
  • Eval 函数执行上下文:执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval,所以在这里我不会讨论它。

执行上下文(Execution Context),是一个对象,由js的执行引擎创建,保存着函数执行所需的重要信息,其中包括了三个属性:

  • 变量对象(variable object)
  • 作用域链(scope chain)
  • this指针

上下文栈/执行栈/函数调用栈
JS在执行进入执行环境后,解析器会创建执行上下文,并将该执行上下文推入执行栈,当前执行环境中代码执行结束后,其对应的执行上下文就会被销毁,并从栈顶出栈,这时执行控制权将交由栈内写一个执行上下文。
这种机制可以在脚本调用多个函数时,跟踪每个函数在完成执行时应该返回的控制点:

  • 当脚本要调用一个函数时,解析器把该函数添加到上下文栈中并且执行这个函数。
  • 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
  • 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
  • 如果栈占用的空间比分配给它的空间还大,那么则会导致“栈溢出”错误。

示例:

function foo () { 
    function bar () {        
      return 'I am bar';
    }
    return bar();
}
foo();

在这里插入图片描述
从该示意图可以看出,上下文栈的特点:

  • 栈底的永远是全局环境的执行上下文:全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被关闭之后它才会从执行栈中被推出,否则一直存在于栈底。
  • 栈顶的是当前正在执行函数的执行上下文:当函数调用完成后,它就会从栈顶被推出

执行上下文的生命周期有两个阶段:

  • 创建阶段(进入执行上下文):函数被调用时,进入函数环境,为其创建一个执行上下文,此时进入创建阶段。
  • 执行阶段(代码执行):执行函数中代码时,此时执行上下文进入执行阶段

执行上下文的属性

1. 变量对象

每次执行一个函数之前,执行引擎都会创建一个上下文对象,创建上下文对象的时候,就会创建它的变量对象,这个过程是:

  • 建立arguments对象赋值:对象的属性名是’0‘,’1‘,’2‘…,属性值就是实际传入的参数。arguments.length是实际参数的个数。
  • 函数声明并赋值:找该函数内的所有函数声明,属性名就是函数名,属性值就是函数的引用。如果有多个同名的函数声明,后出现的函数覆盖前面的属性值
  • 变量声明未赋值:找到这个将要执行的函数内的所有变量声明,存储在变量对象中,属性名就是变量名,属性值是undefined。

理解了变量对象创建的过程,可以理解为什么会有变量提升这个特性了:

  • js在执行函数时会扫描函数代码,将变量声明、函数声明放到变量对象中。
  • 执行函数时如果遇到变量和函数名就去活动对象中查找,发现有对应的属性名就去取出它的属性值;
  • var声明和function声明都具有变量提升的特性,但function的声明在前。

变量对象和活动对象的区别:
当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。

function fun() {
    console.log(a);
    var a =10;
    innerFun();
    return;
    function innnerFun() {
        console.log('Haha,innerFun!');
    }
}
fun();
执行结果:
// undefined
// Haha,innnerFun!

2. 作用域链解析

作用域链是在函数执行之前,与执行上下文一起创建。

作用域链就是变量对象的数组,包含 当前函数的活动对象,及 当前活动函数所在上下文的活动对象,以此类推,最后一个是全局上下文的活动对象。

作用域链的存在主要是保证执行期间,对执行环境(上下文)有权访问的所有变量和函数的进行有序访问。

执行引擎在解析一个变量名的时候,会沿着当前执行函数的作用域链查找活动对象,找到则使用,找不到报错。函数执行完毕后,执行期间创建的变量理论上会被销毁,并从上下文栈中弹出,但是如果js引擎发现你正在使用闭包,那么使用到的变量就不会被清理。

3. this指针如何确定

浏览器严格模式下,函数被调用的时候,调用函数的那个对象会被传递到执行上下文中,成为this的值。
函数调用:

caller.func(arg1,arg2)

等价于

func.invoke(caller, [arg1, arg2])

调用者会被作为参数传递给invoke函数,invoke函数负责创建执行上下文,然后开始执行函数代码,因此,可以理解为:函数this的指向与这个函数在哪里定义无关,只与调用这个函数的对象有关,this在调用时确定而不是定义时。
总结:

  • this指向函数调用者,与定义环境无关
  • 如果函数自主调用(独立调用),那么this指向undefined
'use strict'
var  caller = {
    a: 10,
    func: function func() {
        console.log(this);
    }
}
var ref = caller.func;
caller.func();
ref();     //undefined
console.log(this);      //Window{...}

3种特殊情况:

  • 全局上下文this指向window对象
  • 构造函数中this指向正在构造的对象
  • 通过apply或call调用函数时或通过bind得到函数时,可以自定义this的指向

参考:

  • https://www.jianshu.com/p/635a5986ec73
  • https://juejin.cn/post/6844903812797333512

猜你喜欢

转载自blog.csdn.net/qq_38987146/article/details/115131348
今日推荐