别再懵圈!JS 执行机制的 “千层套路” 全揭秘

作为前端开发者,你是否也曾对着一段看似简单的 JS 代码,发出 "它为啥这么执行?" 的灵魂拷问?比如明明变量还没赋值却不报错,明明声明了变量却提示未定义...... 其实这一切的背后,都是 JS 执行机制在 "搞事情"。今天咱们就扒开 V8 引擎的 "小脑袋",把 JS 执行的底层逻辑聊得明明白白!

一、执行上下文:JS 代码的 "运行大本营"

想要搞懂 JS 执行,先得认识「执行上下文」------ 它就像代码运行的 "专属大本营",每段 JS 代码(全局 / 函数)执行前,都会先创建这个对象,里面装着代码运行所需的所有 "物料"。

执行上下文里核心包含三个部分:

  1. 变量环境 :专门收纳functionvar声明的变量 / 函数,是老派变量的 "专属座位区";
  2. 词法环境let/const的 "新地盘",最大特点是支持块级作用域;
  3. 执行的代码:按从上到下的顺序执行的代码逻辑。

而管理这些 "大本营" 的,就是「执行栈(调用栈)」------V8 引擎用它管理函数调用关系的栈结构,栈顶永远是当前正在执行的上下文,函数执行完就 "弹出" 销毁,主打一个 "来了就占座,走了就清场"。

二、编译先行:JS 执行的 "预习环节"

JS 并非 "边读边执行",而是遵循「先编译,后执行」的原则,编译发生在代码执行前的一瞬间。编译阶段会完成这四件事(函数上下文专属,全局上下文无步骤 3):

  1. 创建执行上下文对象;
  2. 找形参和变量声明,变量名作为 key,值先设为undefined(这就是 var "变量提升" 的根源);
  3. 形参和实参值统一(比如函数传参时,形参会被实参赋值);
  4. 找函数声明,函数名作为 key,值直接赋值为函数体(函数提升优先级比变量高)。

实例 1:变量提升 vs 函数提升(来自 2.js/3.js)

javascript

运行

javascript 复制代码
// 案例1:var变量提升 + 函数提升
showName(); // 输出:函数执行(函数提升,优先执行)
console.log(myname); // 输出:undefined(var变量提升,值为undefined)
var myname = '邓昌兴'; // 执行阶段赋值

function showName() {
  console.log('函数执行');
}

// 编译阶段发生了啥?
// 1. 创建全局执行上下文;
// 2. 找到var myname,存入变量环境:{ myname: undefined };
// 3. 找到function showName,存入变量环境:{ showName: 函数体 };
// 4. 执行阶段:先调用showName(已有函数体),再打印myname(还是undefined),最后赋值。

// 案例2:函数提升优先级高于变量提升(来自3.js)
console.log(func); // 输出:[Function: func](函数提升覆盖变量提升)
function func() {}
var func = '123';
// 编译阶段:先处理var func(设为undefined),再处理function func(赋值为函数体);
// 执行阶段:打印func时,取到的是函数体,而非undefined。

实例 2:函数参数 + 变量提升的 "连环套"(来自 4.js)

javascript

运行

css 复制代码
var a = 1;

function fn(a) {
  // 编译阶段:
  // 1. 形参a入变量环境:{ a: undefined };
  // 2. 实参赋值:a = 3;
  // 3. 找到function a(),覆盖a:{ a: 函数体 };
  // 4. 找到var a = 2(已声明,无操作)、var b = undefined;
  
  console.log(a); // 输出:[Function: a](编译后a是函数体)
  var a = 2; // 执行阶段:a赋值为2
  function a() {}
  var b = a; // 执行阶段:b赋值为2
  console.log(a); // 输出:2
}
fn(3);
// 解析:函数编译时,函数声明优先级 > 形参 > 变量声明,所以第一步打印的是函数体,而非实参3。

三、let/const:变量界的 "新规矩"

let/const作为 ES6 的新特性,彻底打破了var的 "随性",核心特点全藏在词法环境里:

1. 块级作用域:变量的 "区域锁"(来自 5.js/6.js)

javascript

运行

javascript 复制代码
// 案例1:var无块级作用域(来自5.js)
function varTest1() {
  var a = 1;
  if (true) {
    var a = 2; // 同属函数作用域,覆盖外层a
    console.log(a); // 输出:2
  }
  console.log(a); // 输出:2(if块里的a覆盖了外层)
}
varTest1();

// 案例2:let有块级作用域(来自6.js)
function varTest2() {
  var a = 1;
  if (true) {
    let a = 2; // 块级作用域,和外层a互不干扰
    console.log(a); // 输出:2(块内的a)
  }
  console.log(a); // 输出:1(外层的a)
}
varTest2();

// 案例3:块级作用域的精细化管理(来自7.js)
function foo() {
  var a = 1; // 变量环境:a=1
  let b = 2; // 词法环境:b=2
  {
    // 块级词法环境,独立于外层
    let b = 3; // 块内b,和外层b无关
    var c = 4; // var无块级作用域,存入函数的变量环境
    let d = 5; // 块内词法环境:d=5
    console.log(a); // 输出:1(取外层变量环境的a)
    console.log(b); // 输出:3(取块内词法环境的b)
  }
  console.log(b); // 输出:2(取外层词法环境的b)
  console.log(c); // 输出:4(var无块级作用域,能访问)
  // console.log(d); // 报错:d is not defined(块级作用域的d,外层访问不到)
}
foo();

2. 禁止重复声明:变量的 "唯一性校验"(来自 8.js)

javascript

运行

vbnet 复制代码
let a = 1;
// let a = 2; // 报错:Identifier 'a' has already been declared
function a() {} // 同样报错:let声明的a不能重复定义
// 解析:let/const不允许同一作用域内重复声明变量,哪怕是函数声明也不行;而var允许重复声明(后声明覆盖前声明)。

3. 暂时性死区(TDZ):变量的 "未解锁期"

let/const没有变量提升,在声明前访问会直接报错,这个区间就是「暂时性死区」:

javascript

运行

javascript 复制代码
console.log(b); // 报错:Cannot access 'b' before initialization
let b = 2;
// 解析:编译阶段,let变量会被存入词法环境,但不会像var一样赋值为undefined;
// 执行阶段,声明前访问就会触发死区报错。

四、JS 执行流程:一张图总结

plaintext

markdown 复制代码
读取代码 → 编译(创建执行上下文:变量环境+词法环境)→ 执行(按顺序跑代码)
          ↓                    ↓
    处理变量/函数声明        调用栈管理执行上下文(栈顶=当前执行的上下文)

最后:核心总结

  1. JS 执行的核心是「执行上下文」,编译永远发生在执行前;
  2. var/function进变量环境,有提升特性,无块级作用域;
  3. let/const进词法环境,支持块级作用域,无提升、有死区、禁止重复声明;
  4. 调用栈是执行上下文的 "管家",栈顶永远是当前正在执行的代码,执行完就销毁。

搞懂这些,再遇到 "代码为啥这么跑" 的问题,你就能直接 "透视" V8 引擎的执行逻辑,再也不用靠 "猜" 写代码啦!

相关推荐
GuWenyue1 小时前
LeetCode 76 最小覆盖子串|JS 滑动窗口标准解法
前端·算法·面试
YHHLAI1 小时前
前端 HTTP 请求 & LLM 接口开发
前端·网络协议·http
拾年2751 小时前
__proto__ vs prototype:90% 的人分不清的 JavaScript 核心
前端·javascript·面试
国科安芯1 小时前
国科安芯推出商业航天级抗辐照半双工 RS485 收发器 ASC485S2Y
前端·单片机·嵌入式硬件·架构·安全性测试
丑过三八线1 小时前
Umi 运行时配置 app.tsx 详解
前端
提子拌饭1331 小时前
个人月事记录表应用 - 鸿蒙PC Electron框架完整实现指南
前端·javascript·华为·electron·前端框架·开源·鸿蒙系统
超人气王1 小时前
新手学前端JS浅拷贝和深拷贝:对象复制竟然是个“替身文学”?
javascript·面试
YHL1 小时前
📚 JS执行机制(执行上下文 + 调用栈 + 编译流程)
前端·javascript
不简说1 小时前
这次真香!sv-print 可视化打印设计器更新:插件脚手架、Excel 导出、弹窗 API 三连发
前端·javascript·前端框架