重学预编译

简介: 重学预编译
前言:如果这篇文章 对你有帮助,请不要吝啬你的赞。😃

前情回顾

参加掘金日新计划时,在群里看到的问题(改造了下)

var a;

if (true) {
  console.log(a)
  a = 111
  function a() { }
  a = 222
  console.log(a)
}

console.log(a)

基础知识回顾

变量提升

实际上,变量的提升其实算是JS的诟病了,所以es6出来了 let const之后,都是推荐使用 let const了。

在执行函数前,会先预编译,把 var声明的变量的声明提升到前面

首先,假如我们只有一个语句 console.log(a),这样子会直接报错 a is not defined

如果只声明变量,但是不赋值,则会得到 undefined

var a;
console.log(a)

那么,如果先打印 a,之后再定义 a呢?

console.log(a)
var a

首先呢?JS是单线程的,所以JS理论上是从上到下执行代码的,所以按理来说会报错 a is not defined

但是,实际上在执行代码前,会先进行一次预编译,把 var变量的声明提升到前面。

所以上面的代码实际上也相当于

var a
console.log(a)

变量提升只会把变量的声明提升到前面,赋值则不会提升到前面。

console.log(a)
var a = 123
console.log(a)

会先输出 undefined,然后输出 123

预编译后的代码如下,

var a
console.log(a)
a = 123

函数提升

函数声明整体提升,函数调用不提升

console.log(mytest)
console.log(111)

function mytest() {
  console.log(222)
}

console.log(mytest)
console.log(333)

mytest()
console.log(444)

预编译后:

function mytest() {
    console.log(222)
}

console.log(test)
console.log(111)

console.log(mytest)
console.log(333)

mytest()
console.log(444)

image-20220407234205587

使用使用变量声明函数,则走的是变量提升路线,而不是函数声明路线

console.log(mytest)   // undefined
mytest()    // mytest is not a function

var mytest = function () {
  console.log(123)
}

函数内部也会有变量提升,这时候会先预处理全局的,再预处理函数的,且函数内的变量、函数提升不能提升到函数外。

function mytest1() {
  console.log(a)    // undefined
  b()   // 456

  var a = 123

  function b() {
    console.log(456)
  }
}

mytest1()
console.log(a)    //  a is not defined

预编译后的代码:

function mytest1() {
    function b() {
        console.log(456)
    }
    var a
    
    console.log(a)
    b()
    
    a = 123
}

mytest1()
console.log(a)

如果函数内部的变量没有定义,直接赋值,则会直接变成全局变量(应该算是遗留bug,不要这样用)

function mytest1() {
  b()   // 456

  a = 123

  function b() {
    console.log(456)
  }
}

mytest1()
console.log(a)    // 123

那么,是先变量提升,还是先函数提升呢?

有不同意见的欢迎评论。

从结果上看是函数优先,但从过程来看是变量优先

预编译步骤

这是怎么回事呢?

全局预编译

首先先来看一下全局预编译的3个步骤:

  1. 创建 GO对象(Global Object)
  2. 找变量声明,将变量作为GO属性(在浏览器中的话,实际上就是挂载到 window对象上),值为 undefined
  3. 找函数声明,作为 GO属性值为函数体

案例分析:

console.log(111)
console.log(a)

function a() {
  console.log(222)
}

var a = 333

console.log(a)

function b() {
  console.log(444)
}

console.log(b)

function b() {
  console.log(555)
}

console.log(b)
  1. 创建 GO对象,找变量声明

    GO: {
        a: undefined
    }
  2. 找函数声明(会覆盖掉重名的)

    GO: {
        a: function a() {
            console.log(222)
        },
        b: function b() {
            console.log(555)
        } 
    }
  3. 全局预编译过程结束,开始真正的编译过程(把提升的给去掉先)

    console.log(111)
    console.log(a)
    
    a = 333
    
    console.log(a)
    
    console.log(b)
    
    console.log(b)
  4. 结合 GO对象的属性

    console.log(111)
    console.log(a)        // f a() { console.log(222) }
    
    a = 333
    
    console.log(a)    // 333
    
    console.log(b)    // f b() { console.log(555) }
    
    console.log(b)    // f b() { console.log(555) }

局部(函数)预编译

GO对象是全局预编译,所以它优先于AO对象所创建和执行。

首先先来看一下局部预编译的4个步骤:

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

案例分析:

function mytest(a, b) {
  console.log(a)
  console.log(b)
  console.log(c)

  var a = 111
  console.log(a)

  function a() {
    console.log(222)
  }
  console.log(a)

  function a() {
    console.log(333)
  }
  console.log(a)

  var b = 444
  console.log(b)
    
  var c = 555
  console.log(c)
}

mytest(123, 456)
  1. 创建 AO对象
  2. 找形参和变量声明

    AO: {
        a: undefined,
        b: undefined,
        c: undefined
    }
  3. 实参与形参统一

    AO: {
        a: 123,
        b: 456,
        c: undefined
    }
  4. 找函数声明

    AO: {
        a: function a() {
            console.log(333)
        },
        b: 456,
        c: undefined
    }
  5. 局部预编译过程结束,开始真正的编译过程(把提升的给去掉先)

    function mytest(a, b) {
      console.log(a)
      console.log(b)
      console.log(c)
    
      a = 111
      console.log(a)
    
      console.log(a)
    
      console.log(a)
    
      b = 444
      console.log(b)
        
      c = 555
      console.log(c)
    }
    
    mytest(123, 456)
  6. 结合 AO对象的属性

    function mytest(a, b) {
      console.log(a)    // f a() { console.log(333) }
      console.log(b)    // 456
      console.log(c)    // undefined
    
      a = 111
      console.log(a)    // 111
    
      console.log(a)    /// 111
    
      console.log(a)    // 111
    
      b = 444
      console.log(b)    // 444
        
      c = 555
      console.log(c)    // 456
    }
    
    mytest(123, 456)

从结果上看是函数优先,但从过程来看是变量优先,因为变量提升后被之后的函数提升给覆盖掉了。

回归正题

准备好基础知识后,自然就是不忘初心,开始解决最开始的问题

参考:Function declaration in block moving temporary value outside of block?

var a;

if (true) {
  console.log(a)
  a = 111
  function a() { }
  a = 222
  console.log(a)
}

console.log(a)

分析:

  1. 会有两个变量声明 a,一个在块内,一个在块外
  2. 函数声明被提升,并被绑定到内部的块变量上

     var a¹;
     if (true) {
       function a²() {} 
       console.log(a²)
       a² = 111
       a² = 222
       console.log(a²)
    }
    console.log(a¹);
  3. 这么一看,这不是和局部变量提升差不多。但是,当到达原来的函数声明处,会把块变量赋值给外部变量

     var a¹;
     if (true) {
       function a²() {} 
       console.log(a²)
       a² = 111
       a¹ = a²        // 当到达原来的函数声明处,会把块变量赋值给外部变量
       a² = 222
       console.log(a²)
    }
    console.log(a¹);
  4. 之后,块变量和外部变量不再有联系,即块变量变化不会导致外部变量的变化。
  5. 依次输出 f a() {} 222 111

为什么当到达原来的函数声明处,会把块变量赋值给外部变量

the spec says so. I have no idea why. – Jonas Wilms

不要用块级声明式函数

不要用块级声明式函数

不要用块级声明式函数

if (true) {
  function b() {
    console.log(111)
  }

  console.log(b)    // f b() { console.log(111) }
}


console.log(b)        // f b() { console.log(111) }

根据上面的分析:

if (true) {
  function b²() {
    console.log(111)
  }
    
  b¹ = b²            // 没有定义,直接赋值,变为全局变量

  console.log(b²)    // f b() { console.log(111) }
}


console.log(b¹)        // f b() { console.log(111) }

我们把if语句的条件变为false后:

  • if语句的内容不再执行,合理
  • 函数没有被提升到外面

    • 但是考虑到 if条件 false的话,可能不会预编译内容
    • 但是外边的 b却不是报错 b is not defined,而是输出 undefined

为什么?不知道,想不到原因,有人知道的话,评论告诉一下。(不会这样用,纯好奇为什么)

实际上,想要根据条件切换函数,可以用以下形式

let fn

if (true) {
  fn = function () {
    console.log(111)
  }
} else {
  fn = function () {
    console.log(222)
  }
}

fn()
目录
相关文章
|
缓存 前端开发 算法
现代前端开发趋势与技术探索
随着互联网的飞速发展,现代前端开发在不断演进,涌现出一系列新的趋势和技术。本文将深入探讨最新的前端开发趋势,包括响应式设计、前后端分离、组件化开发、性能优化等,并介绍相关的技术实践和工具使用。
|
3天前
|
弹性计算 人工智能 安全
云上十五年——「弹性计算十五周年」系列客户故事(第二期)
阿里云弹性计算十五年深耕,以第九代ECS g9i实例引领算力革新。携手海尔三翼鸟、小鹏汽车、微帧科技等企业,实现性能跃升与成本优化,赋能AI、物联网、智能驾驶等前沿场景,共绘云端增长新图景。
|
9天前
|
存储 弹性计算 人工智能
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
2025年9月24日,阿里云弹性计算团队多位产品、技术专家及服务器团队技术专家共同在【2025云栖大会】现场带来了《通用计算产品发布与行业实践》的专场论坛,本论坛聚焦弹性计算多款通用算力产品发布。同时,ECS云服务器安全能力、资源售卖模式、计算AI助手等用户体验关键环节也宣布升级,让用云更简单、更智能。海尔三翼鸟云服务负责人刘建锋先生作为特邀嘉宾,莅临现场分享了关于阿里云ECS g9i推动AIoT平台的场景落地实践。
【2025云栖精华内容】 打造持续领先,全球覆盖的澎湃算力底座——通用计算产品发布与行业实践专场回顾
|
8天前
|
人工智能 自然语言处理 自动驾驶
关于举办首届全国大学生“启真问智”人工智能模型&智能体大赛决赛的通知
关于举办首届全国大学生“启真问智”人工智能模型&智能体大赛决赛的通知
|
8天前
|
云安全 人工智能 自然语言处理
阿里云x硅基流动:AI安全护栏助力构建可信模型生态
阿里云AI安全护栏:大模型的“智能过滤系统”。
|
9天前
|
编解码 自然语言处理 文字识别
Qwen3-VL再添丁!4B/8B Dense模型开源,更轻量,仍强大
凌晨,Qwen3-VL系列再添新成员——Dense架构的Qwen3-VL-8B、Qwen3-VL-4B 模型,本地部署友好,并完整保留了Qwen3-VL的全部表现,评测指标表现优秀。
661 7
Qwen3-VL再添丁!4B/8B Dense模型开源,更轻量,仍强大
|
4天前
|
人工智能 运维 Java
Spring AI Alibaba Admin 开源!以数据为中心的 Agent 开发平台
Spring AI Alibaba Admin 正式发布!一站式实现 Prompt 管理、动态热更新、评测集构建、自动化评估与全链路可观测,助力企业高效构建可信赖的 AI Agent 应用。开源共建,现已上线!
|
11天前
|
存储 机器学习/深度学习 人工智能
大模型微调技术:LoRA原理与实践
本文深入解析大语言模型微调中的关键技术——低秩自适应(LoRA)。通过分析全参数微调的计算瓶颈,详细阐述LoRA的数学原理、实现机制和优势特点。文章包含完整的PyTorch实现代码、性能对比实验以及实际应用场景,为开发者提供高效微调大模型的实践指南。
786 2