Appearance
块级作用域与函数声明
本节目标:理解“块级作用域与函数声明”的核心思路,并能把它用于实际开发或面试表达。
ES5 规定
函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
// 情况一:if语句if (true) { function f() {}}
// 情况二:trycatch语句try { function f() {}} catch(e) { // ...}
上面两种函数声明,根据 ES5 的规定都是非法的。但是: 浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。
**ES6****块作用域声明函数**
ES6明确允许在块级作用域之中声明函数。块级作用域之中的函数声明语句的行为类似于let,在块级作用域之外不可引用。
function f() { console.log('I am outside!'); }
(function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } }
f();}());
上面代码在 ES5 中运行,会得到“I am inside!”,因为在if内声明的函数f会被提升到函数头部。ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于let,对作用域之外没有影响。但是,如果你真的在ES6浏览器中运行一下上面的代码,是会报错的,这是为什么呢?
// 浏览器的 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在[附录](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics) B里面规定,浏览器的实现可以不遵守上面的规定,有自己的[行为方式](http://stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6)。允许在块级作用域内声明函数。函数声明类似于var,即会提升到全局作用域或函数作用域的头部(只声明不定义)。
同时,函数声明还会提升到所在的块级作用域的头部。注意,上面三条规则只对ES6的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。
根据上述规则,上面的例子实际运行的代码如下:
// 浏览器的 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; };}