既然有了块级作用域,也有函数作用域,函数中有块级作用域,那么函数能不能在块级作用域中声明呢?这个问题真让人疑惑~
1、ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明
以下两种方法是错误的,既不是在顶层作用域,也不是在函数作用域中声明函数:
if (true) {
function f() {}
}
try {
function f() {}
} catch(e) {
// ...
}
虽说以上两种情况在ES5中都是非法的声明方式,但是在浏览器中却没有报错,而是能够正常运行;
2、ES6中明确允许在块级作用域中声明函数
ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。
function f() { console.log('I am outside!'); }
//以下为在块级作用域中声明函数
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
若在ES5中运行,则会发生函数声明提升的现象:
function f() { console.log('I am outside!'); }
//以下为在块级作用域中声明函数
(function () {
function f() { console.log('I am inside!'); } //函数声明提升
if (false) {
}
f(); //I am inside!
}());
在ES6环境中,理论上,会得到“I am outside”,'I am inside!'函数类似于let变量,则没有函数声明提升的过程,调用f()访问不到。
可是就算如此,我们能得到它本该输出的结果,但实际上,跑出来的结果是会报错的,而不能得到“I am outside”。
对于这个问题,ES6中有新的规定,ES6的浏览器的实现可以有自己的行为方式:
(1)允许在块级作用域内声明函数。
(2)函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
(3)同时,函数声明还会提升到所在的块级作用域的头部。
注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
以上代码会报错是因为实际运行的是如下代码
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。采用函数表达式的写法如下:
// 函数声明语句
{
let a = 'secret';
function f() {
return a;
}
}
// 函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}
另外,还有一个需要注意的地方。ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。
// 不报错
'use strict';
if (true) {
function f() {}
}
// 报错
'use strict';
if (true)
function f() {}