重学预编译

重学预编译

前言:原本打算彻底放弃CSDN,进军掘金的,但是看到陆续还有小伙伴关注,最后决定多花一点时间,同步更新。如果这篇文章对你有帮助,请不要吝啬你的赞。😃

前情回顾

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

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

     var;
     if (true) {
       function () {} 
       console.log()= 111=// 当到达原来的函数声明处,会把块变量赋值给外部变量= 222
       console.log()
    }
    console.log();
    
  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 () {
    console.log(111)
  }=// 没有定义,直接赋值,变为全局变量

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


console.log()		// 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()
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值