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()
最后更新时间: 2019-7-7 21:55:38