JavaScript预编译机制深度解析:从V8引擎到执行上下文

一、JavaScript执行流程全景图

JavaScript代码的执行流程可以概括为以下关键步骤:

  1. 读取代码:V8引擎加载JS代码
  2. 预编译阶段:创建执行上下文,进行变量/函数声明提升
  3. 执行阶段:逐行执行代码,进行赋值和函数调用

二、函数预编译四步曲

当函数被调用时,V8引擎会创建函数执行上下文,其预编译过程包含四个关键步骤:

  1. 创建函数执行上下文对象(AO)

    javascript 复制代码
    function example(a, b) {
        var c = 10;
        function d() {}
        var e = function() {};
    }
    example(1,2)
    // 预编译开始:创建函数的执行上下文对象
    example = {}
  2. 寻找形参和变量声明

    • 将形参和变量名作为执行上下文对象的属性名

    • 值为undefined

    javascript 复制代码
    example = {
        a: undefined, // 形参
        b: undefined, // 形参
        c: undefined, // 变量声明
        e: undefined  // 变量声明
    }
  3. 实参与形参值统一

    javascript 复制代码
    example(1, 2); // 调用时实参赋值
    example = {
        a: 1,       // 形参绑定实参值
        b: 2,       // 形参绑定实参值
        c: undefined,
        e: undefined
    }
  4. 找函数声明

    • 函数声明会覆盖同名的变量

    • 在函数体里找函数声明,函数名作为上下文对象的属性名

    • 值为函数体

    javascript 复制代码
    AO = {
        a: 1,
        b: 2,
        c: undefined,
        d: function d() {}, // 函数声明直接赋值
        e: undefined
    }

三、全局预编译三阶段

全局作用域的预编译过程略有不同:

  1. 创建全局上下文对象(GO)

    csharp 复制代码
    // 全局代码
    var a = 'test';
    function fn() {}
    
    // 创建GO对象
    GO = {}
  2. 找变量声明

    • 变量名作为 全局上下文对象的属性名
    • 值为undefined
    css 复制代码
    GO = {
        a: undefined  // 变量声明提升
    }
  3. 找函数声明

    • 函数名作为上下文对象属性名
    • 值为函数体
    javascript 复制代码
    GO = {
        a: undefined,
        a: function fn() {} // 函数声明提升
    }

四、预编译实战解析

看了那么多,来上手试试,下面代码的结果是什么?

1

css 复制代码
   var a=1
   function fn(){
   var a=2
   function a(){}
   var b=a
   console.log(a);
}
 fn()

预编译过程:

第一步,先创建全局上下文执行对象 放入调用栈 在GO中 。第二步查找变量声明 a 值设为undefined。第三步, 然后查找函数声明,函数名fn作为属性名,值为函数体function(){},最后进行代码执行,a=1 并且进入函数体中创建函数的执行上下文对象,查找形参和变量名 ,a值设为undefined ,b值设为undefined,,因为没有形参,所以不需要实参形参值统一 直接在函数体fn中查找函数声明 ,这里需要注意的是变量名和函数名一样的就直接覆盖,不会再创建一个变量,因为是在对象中key是唯一的 ,找到a的函数声明,a值改为函数体,预编译结束,执行代码 执行第3行a值改为2 第4行函数声明不调用不执行,第5行执行b值改为2,第6行输出a,a值为2

结果:

2

js 复制代码
function fn(a) {
  console.log(a);
  var a = 123;
  console.log(a);
  function a() {}
  var b = function () {};
  console.log(b);
  function d() {}
  var d = a;
  console.log(d);
}
fn(1);

第一步,先创建全局上下文执行对象 这里没有变量声明,直接代码执行。第二步,在函数体中创建函数的执行上下文对象,查找形参和变量名 ,形参a值设为undefined,变量a重复 ,b值设为undefinedd值设为undefined。第三步,然后形参和实参值统一 a=1。第四步,在函数体fn中查找函数声明 ,这里需要注意的是var b=fucntion(){}不是函数声明哦,聪明的小伙伴肯定发现了,这是函数表达式,相当赋值要等到执行阶段执行 ,找到d的函数声明,d值改为函数体,预编译结束,执行代码 执行第2行 打印空函数体 执行第3行a值改为123 第4行打印a的结果为123,第5行跳过,第6行输出b值赋为函数体,第7行打印b为空的函数体,第8行跳过,第9行d值改为123,第10行打印d值为123 结果:

3

js 复制代码
function fnn(a, b) {
  console.log(a);
  c = 0;
  var c;
  a = 3;
  b = 2;
  console.log(b);
  function b() {}
  console.log(b);
}

fn(1);

第一步先创建全局上下文执行对象 这里没有变量声明,直接代码执行。第二步在函数体中创建函数的执行上下文对象,查找形参和变量名 ,形参a值设为undefined变量a重复 ,形参b值设为undefinedc值设为undefined,然后形参和实参值统一 a=1,b值没有。第三步在函数体fn中查找函数声明 ,这里需要注意的是var b=fucntion(){}不是函数声明哦,聪明的小伙伴肯定发现了,这是函数表达式,相当赋值要等到执行阶段执行 ,找到b的函数声明,b值改为函数体,预编译结束,执行代码 执行第2行 ,打印结果1, 执行第3行c值改为0 第4行跳过,第5行a值设为3,第6行输出b值赋2,第7行打印b为2,第8行跳过,第9行打印b值为2 结果:

五、关键概念对比表

特性 变量声明 函数声明
提升机制 仅声明提升(值为undefined) 整个函数体提升
执行上下文创建时机 预编译阶段 预编译阶段
重复声明处理 后续声明被忽略 覆盖同名变量
块级作用域影响 受let/const约束 不受块级作用域影响

六、ES6带来的改变

  1. 暂时性死区(TDZ)

    javascript

    ini 复制代码
    console.log(a); // ReferenceError
    let a = 10;
  2. 块级作用域

    javascript

    javascript 复制代码
    {
        let b = 1;
        function fn() {}
    }
    console.log(b); // ReferenceError
    console.log(fn); // 函数可访问(非严格模式)

七、总结

JavaScript的预编译机制是其执行模型的核心:

  1. 全局预编译:创建GO → 变量声明 → 函数声明
  2. 函数预编译:创建AO → 形参/变量 → 实参绑定 → 函数声明
  3. ES6引入的let/const通过块级作用域和TDZ解决了变量提升问题
  4. 理解执行上下文生命周期是掌握JavaScript异步编程、闭包等高级特性的基础

希望本文能帮助你更好地理解 JavaScript 的预编译过程!如果你有任何疑问或想法,欢迎随时交流

相关推荐
是大林的林吖4 分钟前
解决 elementui el-cascader组件懒加载时存在选中状态丢失的问题?
前端·javascript·elementui
鹏仔工作室5 分钟前
elemetui中el-date-picker限制开始结束日期只能选择当月
前端·vue.js·elementui
一 乐7 分钟前
个人博客|博客app|基于Springboot+微信小程序的个人博客app系统设计与实现(源码+数据库+文档)
java·前端·数据库·spring boot·后端·小程序·论文
sTone8737516 分钟前
Android Room部件协同使用
android·前端
晴殇i21 分钟前
前端代码规范体系建设与团队落地实践
前端·javascript·面试
用户740546399430921 分钟前
Vite 库模式输出 ESM 格式时的依赖处理方案
前端·vite
开发者小天28 分钟前
React中使用useParams
前端·javascript·react.js
lichong95137 分钟前
Android studio release 包打包配置 build.gradle
android·前端·ide·flutter·android studio·大前端·大前端++
nvvas1 小时前
npm : 无法加载文件 D:\nvm\nodejs\npm.ps1,因为在此系统上禁止运行脚本问题解决
前端·npm·node.js
拉不动的猪1 小时前
浏览器之内置四大多线程API
前端·javascript·浏览器