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中,函数是一等公民,函数声明的优先级最高,会被提升到当前作用域的最顶端,所以第一次调用时实际执行了下面的函数声明,第二次调用时,由于前边的函数表达式与之前的函数声明同名,故将其覆盖,以后的调用也将会打印出相同的结果。

猜你喜欢

转载自blog.csdn.net/weixin_52148548/article/details/126948852