JavaScript 命名函数表达式
作者:Seiya
时间:2019年06月26日
前言
简单的说,命名函数表达式只有一个用处,那就是在 Debug 或者 Profiler 分析的时候来描述函数的名称,当然,如果你不关注调试,那就没什么可担心的了,否则,如果你想了解兼容性方面的东西的话,你还是应该继续往下看看。我们先开始看看,什么叫函数表达式。
函数声明和函数表达式
在 ECMAScript 中,创建函数的最常用的两个方法是函数表达式和函数声明,两者期间的区别是有点晕,因为规范只明确了一点:函数声明必须带有标示符(Identifier)(就是大家常说的函数名称),而函数表达式则可以省略这个标示符。
函数声明
function function_name(args1, arg2, arg3) {
// function body
}
这里的 function_name 显然是不能忽略的,就是说在函数声明中,你必须给函数起一个名字。
TIP
函数声明会在任何表达式被解析和求值之前先被解析和求值,即使你的声明在代码的最后一行,它也会在同作用域内第一个表达式之前被解析/求值。
函数表达式
function [function_name](args1, arg2, arg3) {
// function body
}
这里的 [function_name] 是可以被忽略的。由此可见,如果不声明函数名称,它肯定是表达式,可如果声明了函数名称的话,如何判断是函数声明还是函数表达式呢?
提示:
ECMAScript是通过上下文来区分的,如果function foo(){}是作为赋值表达式的一部分的话,那它就是一个函数表达式,如果function foo(){}被包含在一个函数体内,或者位于程序的最顶部的话,那它就是一个函数声明。
示例
function foo(){} // 函数声明:因为它是程序的一部分
var bar = function foo(){}; // 函数表达式:因为它是赋值表达式的一部分
new function bar(){}; // 函数表达式:因为它是new表达式
(function(){
function bar(){} // 函数声明:因为它是函数体的一部分
})();
( function foo(){} ); // 函数表达式:包含在分组操作符内,分组操作符,只能包含表达式而不能包含语句
命名函数表达式
var f = function foo(){
return typeof foo; // foo是在内部作用域内有效
};
/* foo在外部用于是不可见的 */
typeof foo; // "undefined"
f(); // "function"
上面例子就是一个有效的命名函数表达式,有一点需要记住:这个名字只在新定义的函数作用域内有效,因为规范规定了标示符不能在外围的作用域内有效
调试器中的函数名
如果一个函数有名字,那调试器在调试的时候会将它的名字显示在调用的栈上。有些调试器(Firebug)有时候还会为你们函数取名并显示,让他们和那些应用该函数的便利具有相同的角色,可是通常情况下,这些调试器只安装简单的规则来取名,所以说没有太大区别,来看一个例子:
function foo(){ return bar(); }
function bar(){ return baz(); }
function baz(){ debugger; }
foo();
// Call stack
// 这里我们使用了3个带名字的函数声明
// 当调试器走到 debugger 语句的时候,调用栈看起来非常清晰明了
baz
bar
foo
expr_test.html()
然后,当函数表达式稍微复杂一些的时候,调试器就不那么聪明了,只能在调用栈中看到问号:
function foo(){ return bar(); }
var bar = (function(){
if (window.addEventListener) {
return function(){
return baz();
};
}
else if (window.attachEvent) {
return function() {
return baz();
};
}
})();
function baz(){ debugger; }
foo();
// Call stack
baz
(?)()
foo
expr_test.html()
归根结底,只有给函数表达式取个名字,才是最委托的办法,也就是使用命名函数表达式。我们来使用带名字的表达式来重写上面的例子(注意立即调用的表达式块里返回的2个函数的名字都是bar):
function foo(){ return bar(); }
var bar = (function(){
if (window.addEventListener) {
return function bar(){
return baz();
};
}
else if (window.attachEvent) {
return function bar() {
return baz();
};
}
})();
function baz(){ debugger; }
foo();
// Call stack
// 又再次看到了清晰的调用栈信息了耶!
baz
bar
foo
expr_test.html()