请问下面代码执行后,输出什么?
var b = 12
;(function b() {
b = 2
console.log(b)
})()
console.log(b)
第一眼看过去,先声明了全局变量b, 然后执行IIFE(立即执行函数表达式),函数内部为变量b
赋值,内部打印输出b
(2), 因为全局变量b
在IIFE内部被修改, 所以第二个console
也输出2。
如果你也是这样想的,那就错了,我们来分析代码执行入栈过程,自底向上:
var b
, 由于变量提升,首先声明变量b,之后为变量b赋值=12;- 执行IIFE:
- 第一个
()
将函数变成表达式,这里将具名函数function b
隐式转换为b = function b()
(这里的变量b名称来自于函数名,保持同名); - 第二个
()
执行了这个函数b()
,执行函数内部代码,b=2
,b指向函数function b
,并试图更改b
的类型和值,然后打印b
; - 执行
console
,打印全局变量b
。
- IIFE的独立词法作用域
在执行IIFE时,会形成内部独立的作用域,外部无法拿到内部声明的变量(内部可以获取到外部变量);所以内部执行function b()
的时候,赋值的变量b
指向的是具名函数function b()
, 即不会变更到外部变量b
,所以第二个console
不受影响,打印12;
既然外部变量b不受影响,那么为什么内部变量b不是输出2呢?这里就要了解具名函数表达式的特性了,我们接着往下分析
- 具名函数表达式
- 函数名标识符私有化
具名函数表达式的函数名不能从外部调用,仅供函数体内部使用,当外部调用时就会报错
var testFun = function a() {
console.log(typeof a) // function
console.log('我是具名函数表达式')
}
testFun()
console.log(a) // 报错,ReferenceError: a is not defined
对于具名函数表达式的函数名a
,只能从函数体内部获取,外面直接访问会引用报错。
- 函数名标识符常量化
具名函数表达式标识符的值不能修改
先看一个例子:
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类型