最近在网上看到这样一个题目:
var x = 1;
function f(x, y = function() {x = 3; console.log(x)}) {
console.log(x)
var x = 2
y()
console.log(x)
}
f()
console.log(x)
// 答案是:undefined, 3, 2, 1
当时虽然把答案作对了,但是对其中的原理其实理解得并不深入,并是抱着追求真理的态度,继续深挖。
翻译过来大概意思是:
如果函数的参数没有默认值,那么形参和函数体内的声明在同一个环境记录中,如果函数的参数有默认值,将会为函数体内的声明重新创建一条环境记录。
也就是说:
- 如果函数参数没有默认值,则生成一个作用域。
- 如果函数参数有默认值,但函数体没有变量声明,或者函数声明,则只生成一个作用域。
- 如果函数参数有默认值,且函数体有变量声明, 变量名也参数名不相同,变量没有赋值,则只生成一个作用域,且忽略该变量。
- 如果函数参数有默认值,且函数体有变量声明, 变量名与参数名相同,变量没有赋值,会生成两个作用域,一个参数作用域,一个函数体作用域。
- 如果参数参数有默认值,且函数体有变量声明,变量有赋值,或者有函数声明,则会生成两个作用域,一个参数作用域,一个函数体作用域。
接下来,通过实际代码来验证一下上面的结论。
先来写一个简单的函数
function add(a, b) {
return a + b
}
add(1, 2)
通过上图可以看到,
add参数没有默认值时,只生成一个作用域Local:add,里面存放了a,b, this三个变量。
接下来,我们将add函数稍稍作一些调整,对函数参数b给定默认值:
function add(a, b = 2) {
return a + b
}
add(1)
通过上图,可以看到,我们给函数
add参数b赋于了默认值,但也只会生成一个作用域Local:add
接下来,我们对add函数再次进行改造,在函数体中声明一个变量。
function add(a, b = 2) {
var x
return a + b
}
add(1)
通过上图,可以看到,在add函数体中声明了一个变量x,但没有对其赋值,并不会在作用域Local:add中去进行声明。
接下来,再次对add函数进行改造:
function add(a, b = 2) {
var a
return a + b
}
add(1)
通过上图,可以看到,当在add函数体中声明了a变量,但没有对其赋值,却会生成两个作用域,一个Local:add,一个Block:add两个作用域中都会存在一个a变量,且都会将实参传递给a变量。
接下来,再次对add函数进行改造:
function add(a, b = 2) {
var x = 1
return a + b
}
add(1)
通过上图,可以看到,如果定义了变量,并对其赋了值,则会生成两个作用域,Local:add, Block:add,
Local:add表示的是参数作用域,存放了a, b两个参数,而Block:add表示函数体作用域,x, this表示。
通过上面这些分析,相信已经对函数作用域有一个大概的认识了。那接下来,我们回到最开始的题目。
var x = 1; // global变量 x:1
function f(x, y = function() {x = 3; console.log(x)}) {
// f函数使用了var声明变量,且参数赋有默认值,所以会生成两个作用域,一个函数参数作用域`Local:f`,一个函数体作用域`Block:f` 参数作用域存放了x, y两个参数变量, 函数体中存放了x, this两个变量。
console.log(x)
var x = 2
y() // 执行y函数时,会访问x, 因为y函数没有定义x,所以会向上查找,找到函数参数作用域中的x,将其修改为3,并打印
console.log(x) // x在函数体中有定义,所以直接打印函数体作用域中的x
}
f()
console.log(x) // 打印全局的x