深入理解JavaScript系列(2) 揭秘命名函数表达式(3)


function foo(){
return bar();
}
function bar(){
return baz();
}
function baz(){
debugger;
}
foo();
// 这里我们使用了3个带名字的函数声明
// 所以当调试器走到debugger语句的时候,Firebug的调用栈上看起来非常清晰明了
// 因为很明白地显示了名称
baz
bar
foo
expr_test.html()


通过查看调用栈的信息,我们可以很明了地知道foo调用了bar, bar又调用了baz(而foo本身有在expr_test.html文档的全局作用域内被调用),不过,还有一个比较爽地方,就是刚才说的Firebug为匿名表达式取名的功能:

复制代码 代码如下:


function foo(){
return bar();
}
var bar = function(){
return baz();
}
function baz(){
debugger;
}
foo();
// Call stack
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()


另外,当把函数赋值给多个变量的时候,也会出现令人郁闷的问题:

复制代码 代码如下:


function foo(){
return baz();
}
var bar = function(){
debugger;
};
var baz = bar;
bar = function() {
alert('spoofed');
};
foo();
// Call stack:
bar()
foo
expr_test.html()


这时候,调用栈显示的是foo调用了bar,但实际上并非如此,之所以有这种问题,是因为baz和另外一个包含alert('spoofed')的函数做了引用交换所导致的。
归根结底,只有给函数表达式取个名字,才是最委托的办法,也就是使用命名函数表达式。我们来使用带名字的表达式来重写上面的例子(注意立即调用的表达式块里返回的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();
// 又再次看到了清晰的调用栈信息了耶!
baz
bar
foo
expr_test.html()


OK,又学了一招吧?不过在高兴之前,我们再看看不同寻常的JScript吧。
JScript的Bug
比较恶的是,IE的ECMAScript实现JScript严重混淆了命名函数表达式,搞得现很多人都出来反对命名函数表达式,而且即便是最新的一版(IE8中使用的5.8版)仍然存在下列问题。
下面我们就来看看IE在实现中究竟犯了那些错误,俗话说知已知彼,才能百战不殆。我们来看看如下几个例子:
例1:函数表达式的标示符泄露到外部作用域

复制代码 代码如下:


var f = function g(){};
typeof g; // "function"


上面我们说过,命名函数表达式的标示符在外部作用域是无效的,但JScript明显是违反了这一规范,上面例子中的标示符g被解析成函数对象,这就乱了套了,很多难以发现的bug都是因为这个原因导致的。
例2:将命名函数表达式同时当作函数声明和函数表达式

复制代码 代码如下:


typeof g; // "function"
var f = function g(){};


特性环境下,函数声明会优先于任何表达式被解析,上面的例子展示的是JScript实际上是把命名函数表达式当成函数声明了,因为它在实际声明之前就解析了g。
这个例子引出了下一个例子。
例3:命名函数表达式会创建两个截然不同的函数对象!

复制代码 代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wdjdxd.html