分析IIFE(立即执行函数表达式)与具名函数表达式

324 阅读3分钟

请问下面代码执行后,输出什么?

var b = 12
;(function b() {
    b = 2
    console.log(b)
})()
console.log(b)

第一眼看过去,先声明了全局变量b, 然后执行IIFE(立即执行函数表达式),函数内部为变量b赋值,内部打印输出b(2), 因为全局变量b在IIFE内部被修改, 所以第二个console也输出2。

如果你也是这样想的,那就错了,我们来分析代码执行入栈过程,自底向上:

无标题-2022-06-12-0008.png

  1. var b, 由于变量提升,首先声明变量b,之后为变量b赋值=12;
  2. 执行IIFE:
  3. 第一个()将函数变成表达式,这里将具名函数function b隐式转换为b = function b()(这里的变量b名称来自于函数名,保持同名);
  4. 第二个()执行了这个函数b(),执行函数内部代码,b=2,b指向函数function b,并试图更改b的类型和值,然后打印b
  5. 执行console,打印全局变量b
  • IIFE的独立词法作用域

在执行IIFE时,会形成内部独立的作用域,外部无法拿到内部声明的变量(内部可以获取到外部变量);所以内部执行function b()的时候,赋值的变量b指向的是具名函数function b(), 即不会变更到外部变量b,所以第二个console不受影响,打印12;

既然外部变量b不受影响,那么为什么内部变量b不是输出2呢?这里就要了解具名函数表达式的特性了,我们接着往下分析

  • 具名函数表达式
  1. 函数名标识符私有化

具名函数表达式的函数名不能从外部调用,仅供函数体内部使用,当外部调用时就会报错

var testFun = function a() {
    console.log(typeof a) // function
    console.log('我是具名函数表达式')
}
testFun()
console.log(a) // 报错,ReferenceError: a is not defined

对于具名函数表达式的函数名a,只能从函数体内部获取,外面直接访问会引用报错。

  1. 函数名标识符常量化

具名函数表达式标识符的值不能修改

先看一个例子:

var testFun = function a() {
    a = 1;
    console.log(a)
}
testFun()

是的,内部为a赋值无效,此时输出函数本身 a() { a = 1 console.log(a) }

回到我们刚开始的面试题,内部为function b赋值无效,所以第一个console.log(b)打印的还是function b;由于IIFE存在内部独立作用域,且内部存在函数同名变量b,所以内部赋值指向的该具名函数表达式,并未影响全局变量b,所以第二个打印还是输出12

当然,如果是下面这种情况,打印出来的b就是2,因为内部b跟具名函数表达式的函数标识符不一样,所以此时内部的b变量通过变量作用域向上查找指向全局变量b, 并重新赋值了全局变量b

var b = 12
;(function c() {
    b = 2
    console.log(b) // 2
})()
console.log(b) // 2
  • 扩展

看下面代码执行输出什么?

var a = 1
function a() {
    console.log('hahah')
}
a()

答案是直接类型报错,TypeError: a is not a function

函数作为一等公民,其提升优先级高于其他变量,所以在上面代码执行中,先将function a(){}提升,然后再var变量提升,此时变量名一致,导致后面的变量a覆盖了之前的function a变量,所以执行a()会报错,此时a为值为1 的number类型