带你真正理清JS中执行上下文和作用域

执行上下文

什么是执行上下文?简单理解就是,js代码在即将执行之前,需要创建一个运行环境,这个环境就是执行上下文,也称为“执行上下文环境”。比如一位厨师要炒菜,炒菜之前需要一口锅,这口锅就是炒菜的“执行上下文”。执行上下文环境分为:全局环境、函数环境以及eval函数环境

那这个执行上下文是用来干嘛的呢?代码在执行之前为什么搭建这个环境呢?其作用其实是为了在代码执行前做一些初始化的准备工作。我们可以将其抽象看为浏览器将在执行代码前创建一个环境对象{},环境对象中具体内容包括:

        1.创建变量对象
        2.确定this指向
        3.确定作用域

创建变量对象

首先在环境对象中创建一个variable object(以下简称vo),既{vo:{}},vo对象中存储内容包括

        (1)函数形参
        (2)初始化赋值后的Arguments对象
        (3)普通字面量声明的函数
        (4)变量声明

我们通过代码来看一下创建过程。

function test(num){
    console.log(num);
    console.log(arguments);
    console.log(typeof getOld);
    const name = 'donk';
    function getOld(){
        console.log('old')
    };
    var getOld = 3;
    console.log(getOld)  //3

}
test(2)

/** 步骤如下

    创建vo对象{vo:{}}

    获取形参 
    {
        vo:{
            num:2
        }
    }

    初始化Arguments对象并赋值为2
    {
        vo:{
            num:2,
            arguments:[2],
        }
    }
                     
    普通字面量形式的函数声明
    {
        vo:{
            num:2,
            arguments:[2],
            getOld:function getOld(){console.log('old')},
        }
    }

    变量声明  注意 这里虽然用var声明了getOld ,但是getOld这个变量之前已经以函数的形式存储过了,所以不再进行存储!
    {
        vo:{
            num:2,
            arguments:[2],
            getOld:function getOld(){console.log('old')},
            name:undefined
        }
    }
*/

这里需要注意的两点,第一点是变量的声明并不会存在赋值操作,也就是我们常说的变量声明提升,他并不会进行赋值,只是声明该变量,真正的赋值操作是在代码执行阶段进行的;第二点是若存在多个相同变量名称的变量,变量对象只会存储第一个

我们先不看后续的this指向和作用域,环境对象初始化完成后,将会立即执行上述代码,根据我们分析得到的变量对象,我们可以很容易、清晰的分析出最终的打印结果。

function test(num){
    console.log(num);  //2
    console.log(arguments);  //[2]
    console.log(typeof getOld)  //function
    const name = 'donk';
    function getOld(){
        console.log('old')
    }
    var getOld = 3;
    console.log(getOld)  //3

}
test(2)

/**
    我们获取到的环境对象
    {
        vo:{
            num:2,
            arguments:[2],
            getOld:function getOld(){console.log('old')},
            name:undefined
        }
    }

    运行第一行代码 console.log(num),根据变量对象,我们可以知道num为2
    运行第二行代码 console.log(arguments),根据变量对象,我们可以知道arguments为[2]
    运行第三行代码 console.log(typeof getOld) 此时getOld是一个函数,所以类型为 function
    运行第四行代码 const name = 'donk' 此时为变量对象name赋值,得到一个新的变量对象
      {
        vo:{
            num:2,
            arguments:[2],
            getOld:function getOld(){console.log('old')},
            name:'donk'
        }
      }
    运行第五行代码(函数声明跳过)var getOld = 3,此时为变量对象getOld重新赋值为3
      {
        vo:{
            num:2,
            arguments:[2],
            getOld:3,
            name:'donk'
        }
      }
    运行第六行代码 console.log(getOld) 此时getOld是赋值后的新值,所以输出3


*/

作用域

作用域通俗理解为我们代码中所声明的变量、函数起作用的地方,粗略也可以认为就是代码声明书写的地方。作用域分为:全局作用域、函数作用域、块级作用域,作用域是静态的,在创建的时候就已经产生了。实际开发中大部分情况下我们真正用到作用域的时候就是“找变量”,找变量的顺序我总结为:先看作用域,再看上下文

作用域是用来干嘛的呢?作用域最大的用处就是用来隔离变量,使不同作用域下同名变量不会存在冲突发生,我们选择函数作用域为例进行讲解。

var a = 3;
function outer(){
    const a =1;
    console.log(a); // 1
}

function inner(){
        const a = 2;
        console.log(a) // 2
}

outer()
inner()

inner函数 和 outer函数都有自己的作用域,所有都会先在自己的作用域中寻找变量a,获取到自己作用域中a的值。

这里我们获取a的过程实际上用到的就是我们上面讲的执行上下文中创建的变量对象。只是大部分情况下我们会自动省略分析创建上下文的过程而直接通过代码获取值。

当在自身作用域中的上下文环境对象中无法找到对应的变量时,我们可以向外层作用域中寻找,如果外层也没有,我们可以继续向外寻找,直到找到我们需要的变量,这种循环向外寻找的过程就是作用域链,作用域是一条从内向外的单向链条,不允许由外向内寻找。

function outer(){
    const a = 1;
    function inner(){
        console.log(a) //1
    }
    inner()
}

outer()

/**
    inner函数的上下文环境对象
    {
        vo:{
            
        }
    }

    outer函数的上下文环境对象(运行到第一行代码const a = 1之后)
    {
        inner:function inner(){console.log(a)},
        a:1
    }
*/

inner函数的作用域中的上下文环境对象中不存在变量a,此时向外寻找,发现outer作用域中的上下文环境对象存在a,则立刻返回该a的值。

以上为全部内容,希望能对大家提供帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值