函数表达式和函数声明
在ECMAjavascript中,创建函数最常用的两个方法是函数表达式和函数声明,这两者的区别并不是很明显。因为ECMA规范只明确了一点:函数声明必须带有标识符(identifier,也就是常说的函数名称),而函数表达式可以省略标识符。
//函数声明
function 函数名称:必须 (函数参数:可选){};
//函数表达式
function 函数名称:可选(函数参数:可选){}
也就是说,如果没有函数名称,那么肯定是函数表达式,常用于:
var foo = function(){};
那么如果有函数名称的时候,就要根据上下文来判断了。
如果是作为赋值表达式的一部分的话,那就是表达式。
如果是被包含在一个函数体内,或者位于程序最顶部,那么就是声明。
var foo = function(){};// 函数表达式
function foo (){}; // 函数声明
new function(){}; // 匿名类表达式
(function(){
function foo(){}; // 声明 因为位于函数体内
})();
还有一种不太常见的表达式写法,就是被括号括住的(function(){}),这是因为在括号是一个分组操作符,在它内部只能是表达式。
function foo(){};//函数声明
(function foo(){}) //函数表达式,因为位于分组操作符内部
try{
(var temp = 3);
}
catch(error){
alert(error);
}
//这里会抛出异常,因为在()中只能放入表达式,而var是声明。
同样的,在使用eval方法,将string类型的json字符串转换为对象的时候,使用eval('('+str +')')也是这个原因。把str放在括号内,就可以使编译器把"{}"解析成表达式而不是代码块。
表达式和声明还有一个很大的差异就是:声明会在同一个作用域内,最早的表达式执行前执行解析。即使函数声明在最后一行,也会优先解析。
alert(foo());
function foo(){
return "abc";
}
//这也就是为什么结果是"abc"的原因,声明虽然写在下面,但是在alert表达式之前已经被执行了。
// 但是如果foo不是以声明的方式定义,而是以表达式的方式定义的话,就会出现问题了:
alert(foo());
var foo = function(){
return "abc";
};
//Uncaught TypeError: foo is not a function 会出现这个错误,因为在alert调用foo的时候,foo并没有被解析。
另外,虽然在条件语句内部可以使用函数声明,但是并没有人测试过兼容性,因此,如果需要“重载”函数,最好还是使用表达式:
if(true){
function foo(){
}
}
else{
function foo(){
}
}
//这种写法存在一定风险,最好使用以下表达式的写法
var foo;
if(true){
foo = function(){};
}
else{
}
命名函数表达式
上面说了一堆函数声明和函数表达式的区别,现在终于言归正传说到正题了。可以看到,上面函数表达式的例子里面,都是没有名字的。
//常规写法:
var foo = function(){};
//命名函数表达式:
var foo = function f(){};
f就是这个表达式的名字,但是,它的作用域仅仅在于函数内部
var foo = function f(){
alert(typeof f);
};
foo();
alert(f);//Uncaught ReferenceError: f is not defined
既然如此,命名还有什么用呢?
答案就是在调试的时候,可以很爽。。。。
var f1 = function (){
return "abc";
};
var f2 = function(){
return f1() + "edf";
};
var f3 = function(){
return f2();
};
alert(f3());
可以看到,当使用命名表达式时,Call Stack会使用该名称。否则会自动起一个名字。可以肯定的是,当情况复杂的时候,解析器并不会返回你期望的那个名字,这也就是为什么要使用命名表达式的原因。
早期版本下,命名函数表达式的bug
- 表达式标识符的作用域泄露到外部作用域,也就是说在函数外部,该标识符也是可见的
- 将函数表达式同时当作了函数声明,即将函数表达式的解析也提到最前面了
- 命名函数表达式会创建两个不同的对象。
var foo = function f(){};
alert(foo === f);// 结果是false,当然这个只有在低版本的ie浏览器下才会看到,正常情况下f应该是undefined
了解到这些bug以后,最好的解决方案就是:当标识符不存在。
因为本身就是方便调试的,那么代码里面直接无视就是最好的办法。
替代方案
如果不想使用“命名”的方式的话,可以还有一种简单的方式来替代,就是在函数表达式中,定义一个函数声明,然后将这个函数声明返回。
var hasClassName = (function(){
var cache = {};
var _className = '(?:^|\\s+)' + className + '(?:\\s+|$)';
var re = cache[_className] || (cache[_className] = new RegExp(_className));
return re.test(element.className);
}
return hasClassName;
})();