• JS的执行上下文,变量声明提升,函数声明提升


    一、什么是执行上下文(execution content)

    一句话:执行上下文就是当前JS代码被解析和执行时存在的环境。(ECMAScript中定义的抽象概念)

    二、执行上下文的类型

    1. 全局执行上下文:这是默认的,最基础的执行上下文,只有一个
    • 不在函数内部的代码都位于全局执行上下文中

    • 创建一个全局对象,其实就是我们的window对象

    • 将this指向这个全局对象

    1. 函数执行上下文:每次函数被调用的时候,就会创建一个新的执行上下文
    • 每个函数都会有自己的执行上下文
    • 一个程序中可以存在任意数量的函数执行上下文
    • 每一个函数执行上下文被创建的时候,它都会按照特定的顺序执行一系列的步骤。
    1. eval函数执行上下文:运行在eval函数中的代码会获得自己的执行上下文。(很少用到,不做讨论)

    三、执行上下文的生命周期(以函数执行上下文为例)

    创建阶段 => 执行阶段 => 回收阶段

    1. 创建阶段

    当函数被调用,但是未执行内部的任何代码之前。

    这个阶段会做出以下几件事:

    • 创建变量对象:首先会初始化函数的参数arguments,提升函数声明和变量声明。

    • 创建作用域链:在执行上下文创建阶段,作用域链是在变量对象之后创建的,作用域链本身包含变量对象,作用域链用来解析变量。

    • 确定this指向

      • function函数是在被调用时确定this指向
      • 箭头函数在声明时就已经确定了this指向,箭头函数的this由其所处的上下文决定
    1. 执行阶段
    • 变量赋值,执行代码
    1. 回收阶段
    • 执行上下文出栈,JS自动执行垃圾回收机制。

    四、变量声明提升

    我们看下边这段代码:

    在变量还没有声明的时候进行打印,会打印出什么呢?

    <script>
           console.log(a);
           var a = 10;
    </script>
    

    结果:
    在这里插入图片描述
    我们看到不但没有报错 Uncaught ReferenceError: Cannot access ‘a’ before initialization(变量a未定义),还打印出了undefined。

    事实上,在执行代码之前,我们的浏览器会先解析一遍我们的脚本,完成一个初始化的步骤,它遇到var变量时就会先初始化变量为undefined。

    这就是变量提升(hoisting),它是指,浏览器在遇到JS执行环境的初始化时,引起的变量提前定义。

    如何避免变量提升呢?

    使用let,const 关键字声明变量,尽量使用从上图,避免使用var

    <script>
           console.log(a);//报错:Uncaught ReferenceError: Cannot access 'a' before initialization
           let  a = 10;
    
           /*--------------------分割线---------------*/
    
           console.log(b);//报错:Uncaught ReferenceError: Cannot access 'a' before initialization
           const b = 20;
        </script>
    

    五、函数声明提升

    • 声明函数的方法(箭头函数属于第二种声明方式)

      • 函数声明方式:function foo(){}
      • 函数表达式方式:var foo = function(){}或var foo = ()=>{}

    我们一起看一段代码:

     <script>
        console.log(f1); //ƒ f1(){}
        function f1(){}
        /*--------------分界线-------------*/
        console.log(f2);//undefined
        var f2 = function(){}
     </script>
    

    同样我们看到,在声明函数之前打印该函数,打印出了内容,这是因为函数提升,就是初始化时,引擎把函数声明整个提升到了当前作用域的顶部。在实际打印之前,函数已经被声明。

    至于第一个与第二个函数打印结果不同,我们结合第四点,变量提升,第二个函数使用函数表达式的方式声明,将函数赋值给了一个变量,这个变量在提升时被初始化为undefined。

    而第一个函数使用函数声明方式声明,打印的是该函数本身。

    注意:

    • 当函数和变量同名时,函数声明的优先级高于变量声明的优先级,因此变量声明会被函数声明给覆盖掉,但是可以重新赋值。
    <script>
    	alert(a)//弹出function a(){alert('我是函数');}
    	var a = "我是变量"
    	function a(){alert('我是函数');}
    </script>
    
    • 如果在同一个作用域中存在多个同名函数声明,后面出现的将会覆盖前面的函数声明。
    function hoistFunction() {
        function foo() {
            console.log(1);
        }
    
        foo(); // 2
    
        function foo() {
            console.log(2);
        }
    }
    
    hoistFunction();
    
    • 函数声明比函数表达式声明优先级高。
    function hoistFunction() {
                foo() // 2
                
                var foo = function () {
                    console.log(1);
                };
    
                foo() // 1
    
                function foo() {
                    console.log(2);
                }
    
                foo(); // 1
            }
    
            hoistFunction();
    //解释JS中,函数是一等公民,函数声明的优先级最高,会被提升到当前作用域的最顶端,所以第一次调用时实际执行了下面的函数声明,第二次调用时,由于前边的函数表达式与之前的函数声明同名,故将其覆盖,以后的调用也将会打印出相同的结果。
    
  • 相关阅读:
    大数据面试之hive重点(二)
    面试(类加载器)
    nodeJs 基础
    【大数据实训】基于当当网图书信息的数据分析与可视化(八)
    proteus 器件名称被软件篡改bug的解决方案
    Cookie、Session、Token、JWT详解
    js中setTimeout定时器不准的原因,以及修正的办法
    基于MYSQL的论坛管理系统数据库设计项目实战
    Codeforces Global Round 21 B. NIT Destroys the Universe
    windows下perforce的命令行操作
  • 原文地址:https://blog.csdn.net/weixin_52148548/article/details/126948852