JavaScript的执行上下文和执行栈

什么是执行上下文?

执行上下文就是当前代码被解析和执行时所在的环境。

  • Global code:默认的执行环境,当代码第一次执行时所在的环境
  • Function code:当进入到一个函数体时的执行环境
  • Eval code :当一段代码传入到eval函数执行时的环境
    在这里插入图片描述
    紫色框包裹的为全局上下文,personfirstNamelastName3个函数上下文,整个代码中只有一个全局上下文,它能够被其他三个上下文访问。

执行上下文栈

浏览器中的JS解析器是单线程的,这意味着一个时间只有一个事情发生(一行代码执行),操作与事件均被压到执行栈里面
在这里插入图片描述
当浏览器第一次加载脚本,默认会进入全局执行上下文,如果在全局代码中调用一个函数,那么程序流将进入这个函数体,创建一个函数执行上下文,同时这个上下文将在执行栈顶部
如果在当前函数的内部调用其他函数,那么同样的过程将会发生,程序流进这个函数体,创建一个新的执行上下文,同时上下文被压入栈顶。浏览器总是执行位于栈顶部的上下文,当一个函数执行完成,它的上下文将从栈顶移除,并将程序的控制权返回给下一层的上下文。

 (function foo(i) {
    if (i === 3) {
        return;
    } else {
        foo(++i);
    }
})(0);

执行栈的变化情况

在这里插入图片描述
上面的代码调用自己三次,均将变量加一。每次函数foo的调用,一个新的执行上下文被创建。一旦完成了执行,将从栈顶移除,并返回下一层执行上下文,直到栈内只剩下全局上下文。

执行上下文关键点

  • JS的代码执行是单线程
  • 执行栈内的代码执行均是同步的
  • 有且只有一个全局上下文
  • 可以有数量不限的函数上下文
  • 每次函数调用均创建一个执行上下文,函数自己调用自己也如此

深入执行上下文

每次函数调用将会创建一个执行上下文。在JS解析器中,每次创建函数上下文将经历两个阶段。
1、创建阶段

  • 创建一个作用域链
  • 创建函数内部的变量、函数和参数
  • this进行复制

2、代码执行阶段

  • 为变量进行赋值,函数引用以及代码执行
executionContextObj = {
    'scopeChain': { ... },
    'variableObject': { ... },
    'this': {}
}

1.scopeChain:包括variableObject,以及所有外层上下文变量
2.variableObject:主要包括函数的参数、内部变量以及函数的声明

变量对象(Variable Object)

在函数调用后,且在函数内代码执行前,会创建一个执行上下文对象executionContextObj,这是我们所说的JS解析器的第一阶段:创建阶段。具体来说,解析器创建一个executionContentObj会先扫描函数传入的参数,以及函数内部的变量声明与函数声明,讲这些参数和声明作为执行上下文对象的属性variableObject

详细过程

1、找到调用函数的函数体
2、在执行函数体之前,创建一个执行上下文对象
3、进入创建阶段

  • 初始化作用域链(scopeChain)

  • 创建一个变量对象

    • 创建参数对象(arguments object),检查参数的上下文,初始化参数的名称和值
    • 扫描上下文中的函数声明
      • 对于每一个函数声明,将在variableObject中创建一个属性,属性名为函数名,并指向改函数对象在内存中的引用
      • 如果该函数已存在,variableObject中的值将会被重写
    • 扫描上下文中的变量声明
      • 对于每一个变量声明,均会在variableObject中创建一个属性,属性名为变量名,同时将该属性的值初始化为undefined
      • 如果该属性已经被声明,那么什么也不做,直接跳过
    • 明确上下文中this的值

4.代码执行阶段

  • 在上下文中执行代码,分配变量值,并逐行依次执行代码
function foo(i) {
    var a = 'hello';
    var b = function privateB() {};
    function c() {}
}

foo(22);

在调用foo(22)后,创建阶段的执行上下文对象

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: undefined,
        b: undefined
    },
    this: { ... }
}

若上所示,在创建阶段只对变量的声明定义(函数参数除外),并没有为他们分配值。一旦函数阶段创建完成,程序流将进入代码执行阶段,在函数完成执行后,执行上下文对象fooExecutionContext

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: 'hello',
        b: pointer to function privateB()
    },
    this: { ... }
}

命名提升

命名提升是指:在函数内部的变量声明与函数声明被提到函数域的顶部

(function() {

    console.log(typeof foo); // function pointer
    console.log(typeof bar); // undefined

    var foo = 'hello',
        bar = function() {
            return 'world';
        };

    function foo() {
        return 'hello';
    }

}());

我们通过问答的形式来探讨命名提升的发送过程
1.为什么可以在foo声明前访问到?

  • 在创建阶段,所有变量都已经被创建,因此在函数执行的时候,变量foo已经定义在上下文的变量对象中

2.foo声明了两次,为什么foo被指向function,而不是undefined或者string

  • 尽管foo声明了两次,然而在创建阶段时,函数声明将先于变量被定义到上下文对象中
  • 因此,一个指向function foo()的引用首先被创建,接着,当解释器扫描到var foo,已经看到属性名foo存在了,什么也不会做,直接跳过

3.为什么的bar的类型为undefined

  • bar是 一个指向函数表达式的变量,在创建阶段变量的值会被初始化为undefined

原文链接 http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/#first-article

发布了17 篇原创文章 · 获赞 0 · 访问量 404

猜你喜欢

转载自blog.csdn.net/k19970320j/article/details/104377890
今日推荐