zoukankan      html  css  js  c++  java
  • 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引擎就会为该函数创建一个新的执行环境,并且将其压入到执行环境堆栈的顶部。浏览器总是执行当前在堆栈顶部的执行环境,一旦执行完毕,该执行环境就会从堆栈顶部被弹出,然后,进入其下的执行环境执行代码。这样,堆栈中的执行环境就会被依次执行并且弹出堆栈,直到回到全局执行环境。 

  • 相关阅读:
    一文带你看清HTTP所有概念
    程序员不得不了解的硬核知识大全
    看完这篇HTTP,跟面试官扯皮就没问题了
    ReentrantLock 源码分析从入门到入土
    计算机网络的核心概念
    Kafka 的这些原理你知道吗
    2019 我是怎么熬过来的?
    不懂什么是锁?看看这篇你就明白了
    机器学习——方差、协方差与皮尔逊值
    最小生成树的本质是什么?Prim算法道破天机
  • 原文地址:https://www.cnblogs.com/yu-hailong/p/8638868.html
Copyright © 2011-2022 走看看