var为什么会变量提升?一盏茶的功夫让你彻底熟悉预编译 ——小白请看

前言

预编译是JavaScript中一个重要的概念,经常出现在面试中。JavaScript中的"var"变量提升和预编译是紧密相关的概念,它们在理解JavaScript中变量声明和作用域的工作方式时非常重要。今天我们来深入聊聊预编译,让大家理解预编译和变量提升的底层逻辑。本文会使用简单的例子以及通俗易懂的语言,小白没压力。

变量提升(Hoisting)

我们先来看一串代码:

js 复制代码
    var a = 123
    console.log(a);

很明显,答案输出123.让我们对这串代码改动一下。

js 复制代码
    console.log(x); // 输出undefined
    var x = 5;

按正常逻辑来说,这串代码应该会报错,但是我们可以发现实际上输出的是undefined,这是因为使用var声明变量会出现变量提升的效果,就相当于:

js 复制代码
    var a
    console.log(a);
    a = 5
    
    这就是变量提升

在JavaScript中,变量声明(使用var关键字)会被"提升"到其作用域的顶部,这意味着在变量声明之前使用变量是合法的,尽管在代码中实际声明变量的位置之前。这是因为JavaScript引擎在执行代码之前会将变量声明提升到作用域的顶部。又比如:

js 复制代码
function example() {
      console.log(x); // 输出 undefined        
      var x 
      X = 5
      console.log(x); // 输出 5
}

example();

相当于:

js 复制代码
function example() {
      var x
      console.log(x); // 输出 undefined
      x = 5;
      console.log(x); // 输出 5
}

example();

那为什么会变量提升呢?JavaScript的编译过程通常分为两个主要步骤:预编译(Compilation)和执行(Execution)。预编译是指JavaScript引擎在实际执行代码之前,对代码进行一些处理,包括变量提升和函数声明。在预编译阶段,JavaScript引擎会扫描代码,找到所有变量声明和函数声明,并将它们提升到适当的作用域。

预编译

  • 预编译发生在函数执行之前

  • 预编译发生在全局区

预编译发生在函数执行之前

  1. 创建AO对象 (Action Object)
  2. 找形参和变量声明,将变量声明和形参作为AO的属性名,值为undefined
  3. 将形参和实参值统一
  4. 在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体

就拿上述为例:

js 复制代码
function example(x) {
      console.log(x);   //输出   2      
      var x 
      x = 5
      console.log(x);  //输出  5
}

example(2);
js 复制代码
//1.首先我们先创建一个AO对象
AO:{
        //2.开始找变量声明和形参
    x : undefined
    //3.将形参和实参值统一
    x :undefined ==> x : 2
    //4.由于函数体内没有函数声明,跳过该步骤
    
}

我们已经完成一次预编译了,现在函数开始执行,由上到下,当函数执行到第一个 console.log(x) 时,它将从AO对象中寻找x的值 ,输出为2.函数继续向下执行,变量声明,跳过,当发现赋值语句 x = 5 时,将AO对象中x的值改变为5 . 函数继续向下执行到 第二个console.log(x) 时,输出为 5.

相信看到了这里,小伙伴们对预编译已经有一定了解了,接下来我们一起来看看一道有点难度的题:

js 复制代码
function foo(a, b) {
    console.log(a);  //输出 1
    c = 0
    var c;
    a = 3    //  a : undefined ==> 3   函数执行时
    b = 2    //  b : undefined ==> 2
    console.log(b);  // 输出 2
    function b() { }
    function d() { }
    console.log(b);  // 输出 2


}
foo(1)

接下来我们看看是怎么预编译的: `

js 复制代码
    AO:{                                
    a : undefined   ==> a : 1 //统一值                 
    b : undefined   ==> b : function b(){}     
    c : undefined   ==> d : function d(){}
                            //函数声明,属性名为函数名,值赋予函数体
}

当编译完成后再去执行函数,这样我们就很容易得出答案啦。

预编译发生在全局区

  1. 创建GO对象 (Global Object)
  2. 找变量声明,将变量声明作为GO的属性名,值为undefined
  3. 在全局找函数声明,将函数名作为GO对象的属性名,值赋予函数体

我们直接上例子看看是怎么个事

js 复制代码
var a = 100
function fn() {
    console.log(a);
}
js 复制代码
GO:{//找变量声明
    a : undefined
    //找函数声明
    fn : function fn()
}
fn()

但我们可以发现,函数预编译和全局预编译在一段代码中通常一起发生,比如上图,当预编译全局完成时 ,开始执行,当执行完 a = 100后,准备执行函数fn(),这时预编译发生在函数执行时

再来一个例子,他们结合在一起时:

js 复制代码
a = 100
function fn() {
    console.log(a);   // 输出  100
    b = 200
    var b          // 输出   200
    console.log(b)     
}
fn()
var global

预编译完成之后 ,我们发现这样GO对象和AO对象都会存在:

js 复制代码
GO: {
a : undefined  
fn : function fn()
}

AO: {
b : undefined
}

AO里面没有a,为什么当console.log(a)时会输出呢?而我们发现,GO函数里有a,是不是用了GO里面的a呢?这里我们就要引出调用栈的概念了

调用栈

上面所说的AO、GO对象其实是属于执行上下文的一部分,便于我们理解

调用栈用于跟踪函数的调用顺序和执行上下文的管理。每当函数被调用,一个新的执行上下文会被推入调用栈,表示该函数的执行。 全局预编译和函数预编译的结果在执行上下文中存储,然后被推入调用栈。调用栈的顶部始终包含当前正在执行的函数的上下文。概念可能有点难懂,接下来我们上图理解:

全局执行上下文分为变量环境和词法环境,而我们用var声明的变量存在变量环境中。我们都知道栈是先进后出,为什么这里全局执行上下文在栈底部呢,也就是比fn执行上下文先进来,因为在调用栈中,全局预编译通常会在函数预编译之前完成,因此全局预编译的结果位于调用栈的底部,而函数的预编译结果则根据函数调用的顺序依次位于调用栈中。

当函数执行时,需要输出a,但是在fn执行上下文里面并没有找到a这个东西,如果变量在当前函数的执行上下文找不到,JavaScript引擎会在全局执行上下文中寻找变量,如下图。

这也就解释了为什么最后可以输出a。

总结

当我们了解了预编译及调用栈时,我们再遇到这种问题就不用害怕了。而这类问题在面试题中也很容易遇到,小伙伴们一定要了解底层逻辑,在以后做题或面试时根本不慌。

今天的内容就到这啦,如果你觉得小编写的还不错的话,或者对你有所启发,请给小编一个辛苦的赞吧

相关推荐
软件测试慧姐1 小时前
高级自动化测试常见面试题(Web、App、接口)
软件测试·面试
狼性书生2 小时前
uniapp vue3实现的一款数字动画调节器件,支持长按、单点操作,提供丝滑的增减动画效果
前端·vue.js·微信小程序·小程序·uni-app
Jelena157795857922 小时前
爬虫获取微店商品快递费 item_feeAPI 接口的完整指南
开发语言·前端·爬虫
总是学不会.2 小时前
从“记住我”到 Web 认证:Cookie、JWT 和 Session 的故事
java·前端·后端·开发
xinran_Yi2 小时前
XSS-labs靶场通关
前端·xss
前端代码仔3 小时前
JS继承的几种实现方式
前端·javascript
巅峰赛2000分以下是巅峰3 小时前
buuctf.web 64-96
前端
伶俜Monster3 小时前
Threejs 光照教程,为 3D 场景注入灵魂
前端·3d·webgl·threejs
哔哩哔哩技术3 小时前
从 React 看前端 UI 代码范式革命
前端
程序饲养员3 小时前
Cloudflare Functions的SSR相对于原生 Cloudflare Workers性能损耗几何?
前端·javascript·前端框架