欢迎光临
我们一直在努力

搞懂JS执行机制,5分钟搞定变量提升/作用域/调用栈核心问题,面试提问底层时再也不怕!

你是不是也经常被JS的执行顺序搞懵?

明明变量在后面定义,前面却能访问?
let/const和var到底有啥不一样?
函数执行时的变量查找规则总是记不住?

这篇文章用最通俗的语言+可运行的实战代码,带你彻底搞懂JS执行机制的核心逻辑。
读完你能解决:

  • 变量提升的底层原因
  • let/const的暂时性死区问题
  • 函数编译执行的完整流程
  • 块级作用域的实际应用场景

一、先搞懂:JS执行的两个核心阶段

JS代码不是简单从上到下执行,而是分为编译阶段和执行阶段。
这是理解所有执行机制的基础!

1. 编译阶段:提前做好"准备工作"

V8引擎在执行代码前,会先编译生成执行上下文对象,这个过程会做3件关键事:

  • 找形参和变量声明(var/function),变量名作为key,值设为undefined
  • 找函数声明,函数名作为key,值为完整函数体(优先级高于变量),假如遇到同名的参数,函数会覆盖掉变量声明
  • 形参和实参赋值(仅函数执行上下文有这一步)
    在这里插入图片描述
    • 从上面这张图片可以看到,执行上下文先运行环境再去执行代码

    2. 执行阶段:按顺序执行代码

    编译完成后,才会从上到下执行代码,给变量赋值、执行函数调用等操作。

    二、实战1:变量提升与函数提升的坑

    先看一段代码,猜猜执行结果?

    // 实战代码1:变量提升
    showName(hwq)
    console.log(myname)
    var myname = "hwq"
    function showName(name) {
    console.log("函数showName 执行了"+name)
    }

    执行结果:

    函数showName 执行了hwq
    undefined

    底层原因:

    编译阶段V8做了这些事:

  • 声明变量myname,值为undefined(变量提升)
  • 声明函数showName,值为完整函数体(函数提升,优先级更高)
  • 执行阶段先调用showName(已存在),再打印myname(还没赋值),最后给myname赋值。
  • 在这里插入图片描述

    踩坑提醒:

    • 函数提升优先级 > 变量提升,这就是函数的优先
    • var声明的变量会提升,值为undefined,容易导致预期外的undefined

    再看一个更易踩坑的例子:

    // 实战代码2:函数与变量同名
    console.log(func)
    function func(name) {
    console.log("函数showName 执行了"+name)
    }
    var func = 'hwq'

    执行结果:

    [Function: func]

    原因:

    编译阶段函数声明先生效,变量声明不会覆盖函数,只有执行阶段的赋值才会改变。

    三、实战2:函数执行时的变量处理

    这段代码的执行结果是什么?

    // 实战代码3:函数内的变量声明
    var a =1;
    function fn(a){
    console.log(a)
    var a=2
    function a() {}
    var b=a;
    console.log(a)
    }

    fn(3);

    执行结果:

    [Function: a]
    2

    拆解编译过程(fn函数执行上下文):

  • 形参a声明,赋值为3
  • 找变量声明:var a、var b → 先记为undefined(但不会覆盖已有形参)
  • 找函数声明:function a() → 覆盖a的值为函数体
  • 执行阶段:
    • 第一个console.log(a) → 打印函数体
    • 给a赋值2 → a变成2
    • b赋值为a(2)
    • 第二个console.log(a) → 打印2
  • 踩坑提醒:

    函数内部的变量/函数声明,编译阶段会先处理,执行阶段的赋值才会最终生效。

    四、let/const:和var的核心区别

    var的作用域是函数级,let/const是块级作用域,还有暂时性死区!

    实战4:块级作用域对比

    // 实战代码4:let的块级作用域
    function varTest (){
    var x=1;
    if(true){
    let x=2;
    console.log(x) // 块内的x
    }
    console.log(x) // 函数级的x
    }
    varTest()

    // 实战代码5:嵌套块级作用域
    function foo(){
    var a =1;
    let b=2;
    {
    let b=3; // 块级作用域的b
    var c=4; // var无块级作用域,属于函数级
    let d=5; // 块级作用域的d
    console.log(b) // 3
    console.log(c) // 4
    console.log(d) // 5
    }
    console.log(b) // 2(外层的b)
    // console.log(d) // 报错:d is not defined
    }
    foo()

    执行结果:

    2
    1
    3
    4
    5
    2

    在这里插入图片描述

    核心区别(加粗划重点):

  • 块级作用域:let/const受{}限制,var不受
  • 暂时性死区:let/const不会变量提升,声明前访问会报错
  • 重复声明:let/const不能重复声明同一变量,var可以
    • 重点提醒!:词法环境也是一种栈结构,这样可以看就近原则,且词法环境时有块级作用域,所以let const 不会像var那样把变量提升到外面的区域里面

    踩坑提醒:

    // 错误示例:let重复声明
    let a=1;
    let a=2; // 直接报错:Identifier 'a' has already been declared

    五、调用栈:管理函数执行的"容器"

    V8引擎用调用栈(执行栈) 管理函数调用关系:

  • 全局代码编译后,全局执行上下文入栈
  • 调用函数时,函数执行上下文入栈(成为栈顶)
  • 函数执行完毕,执行上下文出栈销毁
  • 栈顶指针永远指向当前正在执行的上下文
  • 通俗理解:

    调用栈就像叠盘子,新调用的函数是最上面的盘子,执行完就拿走,始终操作最顶部的盘子。

    六、总结:JS执行机制核心要点

  • JS执行分编译(生成执行上下文)和执行两个阶段
  • 变量提升/函数提升是编译阶段的产物,函数提升优先级更高
  • var是函数级作用域,let/const是块级作用域+暂时性死区
  • 词法环境也是一种栈结构,以块级作用域来执行出栈入栈的操作
  • 调用栈管理执行上下文,栈顶是当前正在执行的函数/全局
  • 编译阶段先处理声明,执行阶段处理赋值和函数调用
  • 掌握这些核心逻辑,你就能轻松解释90%的JS执行顺序问题,再也不会被变量提升、作用域坑到!

    赞(0)
    未经允许不得转载:171主机测评 » 搞懂JS执行机制,5分钟搞定变量提升/作用域/调用栈核心问题,面试提问底层时再也不怕!
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址