js执行上下文

1.什么是执行上下文

JavaScript是一个单线程语言,意味着同一时间只能执行一个任务。当JavaScript解释器初始化执行代码时, 它首先默认进入全局执行环境(execution context),从此刻开始,函数的每次调用都会创建一个新的执行环境。

2.执行环境的分类

  • 全局环境——JavaScript代码运行时首次进入的环境。
  • 函数环境——当函数被调用时,会进入当前函数中执行代码。
  • Eval——eval内部的文本被执行时(因为eval不被鼓励使用,此处不做详细介绍)。

3.执行上下文栈

概念

当JavaScript代码执行的时候,会进入不同的执行上下文,这些执行上下文会构成了一个执行上下文栈(Execution context stack,ECS)。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。

代码在执行过程时遇到以上三种执行环境的代码时,都会生成一个对应的执行上下文,压入执行上下文栈中,当栈顶的上下文执行完毕之后,会自动出栈。下面用一个例子说明,可参考上面的执行环境栈图片

 

var a = 1;
function fn1() {
  function fn2() {
   console.log(a);
 }
 fn2();
}
fn1();

第一步,全局执行上下文入栈。

第二步,遇到fn1(),执行代码,创建自己的执行上下文,入栈。

第三步,fn1的上下文入栈之后,接着执行其中的代码,遇到fn2(),创建自己的执行上下文,入栈。

第四步,在fn2的执行上下文中未创建新的执行上下文,代码执行完毕之后,fn2的执行上下文出栈。

第五步,fn2的执行上下文出栈之后,继续执行fn1的可执行代码,也未创建新的执行上下文,出栈。这个时候栈中只剩下全局执行上下文了。

有5个需要记住的关键点,关于执行栈(调用栈):

  • 单线程。
  • 同步执行。所有的执行上下文都得等到栈顶的执行之后才能顺序执行
  • 只有一个全局执行上下文。
  • 函数上下文是无限制的。
  • 每次函数被调用时都会创建新的执行上下文,包括调用自己

3.执行上下文的构成

三个重要的属性,变量对象(Variable object,VO),作用域链(Scope chain)和this。这三个属性跟代码运行的行为有很重要的关系

变量对象(Variable object)

变量对象(缩写为VO)是一个与执行上下文相关的特殊对象

变量对象是与执行上下文相关的数据作用域。它是一个与上下文相关的特殊对象,其中存储了在上下文中定义的变量和函数声明。也就是说,一般VO中会包含以下信息:

  • 变量 (var, Variable Declaration);
  • 函数声明 (Function Declaration, FD);
  • 函数的形参

在JavaScript解释器内部,每次调用执行上下文,分为两个阶段:

创建阶段(此时函数被调用,但未执行内部代码):

  • 设置[[Scope]]属性的值
  • 设置变量对象VO,创建变量,函数和参数。
  • 设置this的值。

激活/代码执行阶段:

在当前上下文上运行/解释函数代码,并随着代码一行行执行指派变量的值和函数的引用。

创建阶段

1.根据函数的参数,创建并初始化arguments object。

2.扫描上下文的函数声明:对于找到的函数声明,将函数名和函数引用存入VO中,如果VO中已经有同名函数,那么就进行覆盖。

3.扫面上下文的变量声明:对于找到的每个变量声明,将变量名存入VO中,并且将变量的值初始化为undefined。如果变量的名字已经在变量对象里存在,不会进行任何操作并继续扫描。

要记住:函数扫描是在变量之前。

4.提升(Hoisting)

(function() {
    console.log(typeof name); // function
    console.log(typeof another); // undefined
    var name = 'Abby',
        another = function() {
            return 'Lucky';
        };
    function name() {
        return 'Abby';
    }
    console.log(typeof name); // string
    console.log(typeof another); // function
}())

此时的创建阶段的过程是:

1.函数name和其引用被存入到VO之中。

2.变量name发现在VO之中存在同名的属性,因此忽略。

3.变量another存入到VO之中,并赋值为undefined。(这也是函数表达式不会提升的原因)

此时代码从上到下执行的时候激活阶段的过程是:

1.console.log(typeof name); 此时name在VO中是函数。

2.console.log(typeof another); 此时another在VO中的值是undefined。

3.指出函数name的引用。

4.将name赋值为’Abby’。

5.将another赋值为函数表达式的值。

6.console.log(typeof name); 此时的name由于被函数被字符串赋值覆盖因此是string类型。

7.console.log(typeof another); 此时的another被赋值成函数表达式因此是function类型。

因此理解执行上下文之后也就很好理解了为什么我们能在name声明之前访问它,为什么之后的name的类型值发生了变化,为什么another第一次打印的时候是undefined等等问题了。

5.小结

当javascript代码被浏览器载入后,默认最先进入的是一个全局执行环境。当在全局执行环境中调用执行一个函数时,程序流就进入该被调用函数内,此时JS引擎就会为该函数创建一个新的执行环境,并且将其压入到执行环境堆栈的顶部。浏览器总是执行当前在堆栈顶部的执行环境,一旦执行完毕,该执行环境就会从堆栈顶部被弹出,然后,进入其下的执行环境执行代码。这样,堆栈中的执行环境就会被依次执行并且弹出堆栈,直到回到全局执行环境。 

posted @ 2018-03-24 14:34  开始战斗  阅读(236)  评论(0编辑  收藏  举报