从一道题分析变量提升和函数提升的细节

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

这道题,你能做对吗?

最近重新看执行上下文栈时,发现下面这道题非常有意思:

题目:

var a = 1;
function foo(a){
    var a = 2;
    console.log("inner a", a);
    function a(){
        console.log("It's function declaration")
    }
    console.log("inner a1, and typeof a1 is", a, typeof(a))
    var a = function(){
        console.log("It's a function expression")
    }
    console.log("inner a2, and typeof a2", typeof(a))
    
}
console.log("outer a1", a)
foo(1);
console.log("outer a2", a)
复制代码

答案揭晓:

image.png

JavaScript 变量对象

我们先来看看什么是变量对象:

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。JavaScript 深入变量对象

根据打印结果,我们来回顾一下代码的执行顺序:

1. 创建执行上下文栈

JavaScript 引擎创建执行上下文栈来管理执行上下文,执行上下文分为全局执行上下文和函数执行上下文。它们都涉及到三个要素:活动对象、作用域链、this, 它们的值根据代码的初始化和执行阶段变化。

执行全局代码,用数组模拟为:

```js
ECStack = [
    globalContext
]
```
复制代码

2. 初始化

全局上下文初始化,同时,foo 函数被创建,保存作用域链到函数的的内部属性 [[scope]]

```js
 globalContext: {
     VO: [global],
     Scope: [globalContext.VO],
     this: globalContext.VO
 }
```

```js
foo.[[scope]] = [
    globalContext.VO
]
```
复制代码

3. 创建foo 函数上下文

执行foo 函数,创建 foo 函数执行上下文,并把 foo 执行上下文压入执行上下栈。

    ECStack = [
        globalContext,
        fooContext
    ]
复制代码

4. foo 函数初始化

(1)复制 foo 函数的 [[scope]] 属性创建作用域链

(2)用 arguments 创建活动对象

(3)初始化活动对象,即加入形参、函数声明、变量声明

   AO = {
       arguments: {
          0: 1,
          length: 1
       }
   },
   a: 1, // 函数的形参
复制代码

(4)将活动对象压入foo作用域链顶端

5. foo 函数执行

沿着作用域链查找 scope 值,返回 scope,执行完毕后, foo上下文从执行上下文栈弹出。执行时,变量 a 多个赋值语句修改,同名变量还会覆盖前一步的值。

但是,函数声明的变量 a 明明在变量 a的后面呀,为什么最后打印结果不是该函数呢?

变量和函数重名, 函数优先提升!

同名的变量声明或者函数声明,都是后者覆盖前者。

同名的变量声明和函数声明,函数声明优先提升,因此同名的变量声明会覆盖函数声明。

把函数表达式赋值给同名变量,等于是对该同名变量进行了赋值操作,因此最后变量的值是被修改后的值。

又是 var!

这段代码还留了一个var的坑,即全局作用域下声明了变量 a,但是 foo 函数中也用 var 声明了变量 a,这里的打印结果可以参照下面两段段代码思考一下:

第一段代码:


var a = 1;
function foo(a){
    a = 2;
    console.log("inner a", a);
    
}
console.log("outer a1", a)
foo(1);
console.log("outer a2", a)
复制代码

第二段代码:


var a = 1;
function foo(a){
    var a = b = 2;
    console.log("inner a", a);
    
}
console.log("outer a1", a)
foo(1);
console.log("outer a2", a, b)
复制代码

具体就不分析了,这和作用域有关,可以参照上面的执行步骤分析一下。但是我们自己写代码的时候,为了不坑自己,还是尽量使用 let 替代吧。

总结

将代码一步一步调试之后,可以发现:

(1)同名变量或者同名函数声明,后者都会覆盖前者

(2)函数的形参如果与该函数作用域中的变量同名,变量的值会覆盖该行参的值

(3)同名的变量和同名的函数声明,函数声明会优先提升,因此会造成即使函数声明在变量赋值表达式的后面,也可能会被同名的变量的赋值操作覆盖掉原来的值。

Supongo que te gusta

Origin juejin.im/post/7069768449530167309
Recomendado
Clasificación