继续汤姆大叔的js之旅。
揭秘命名函数表达式
函数表达式和函数声明
汤姆大叔在博客中引用ECMA规范:函数声明必须带有标识符,函数表达式可以省略。对于我来说这些概念的东西真是不所适从。还是大叔的实例带劲。上实例如下:
function foo(){};//鬼都知道是声明
var bar = function foo(){};//鬼也知道是表达式
new function bar(){};
(function() {
function bar(){};//这也是声明
})();这些大家看看应该都很好理解。在这里大叔还总结了一点可以在大部分情况下快速的判断是表达式还是函数声明"赋值一定是表达式,不带函数名一定是表达式"。
还有一种函数表达式是不太常见,就是用括号括住的(function foo(){})。这个是表达式的原因很简单括号()是一个分组操作符,而更重要的是分组操作符的内部只能包含表达式。
函数语句
虽然提到这个,但是现在可能还是存在不支持(chrome浏览器我看是支持的)(这点儿我自己没有细想也没有完全的去理解)。
命名函数表达式
直接上实例var bar = function foo(){};这是有效的命名函数表达式,大叔特意强调新定义函数作用域内有效。看大叔的这个例子吧,看懂就理解了:
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();
我们来看看这些函数调用过程:foo->bar;而bar是一个命名函数表达式,由内部的两次返回之后是baz,也就是说baz被bar调用。bar->baz;baz内部调用debugger。
js的bug
- 函数表达式的标识符泄露到外部的作用域;实例1
- 将命名函数表达式同时作为函数声明和函数表达式;实例2
- 命名函数表达式创建两个截然不同的函数对象;实例3
- 仅仅顺序解析函数声明而忽略条件语句块;实例4
实例1:var f = function g(){};typeof g;//“function”;亲自在IE8上测试成功。这是不应该的,标识符g被解析成函数,可能前端程序员在写程序的好多时候的bug就是这样引起的。
实例2:var typeof g;var f = function g();同样在ie8下是function,在chrome下是undefined
实例3:var f = function g(){};f === g;f.expando = 'foo';g.expendo;测试chrome下不能运行(显示g没有定义)。
实例4:var f = function g(){return 1;};if(false){f=function g(){return 2;};} g();测试chrome下是g没有定义。
这么多问题,其实都是把标识符作为一个函数声明了(个人理解)。
js内存管理
先看大叔给的实例,然后我们来理解大叔给我们带来的js内存管理这些内容。var f = (function(){if(true){return function g(){};}return function g(){};})();。在匿名函数调用返回的函数(就是带有标识符g的函数) ,然后赋值给外部的f;这个对象与返回的函数对象不是一个事情,多余的g函数就死在返回函数的闭包中。这就是内存问题的产生原因。
(个人对这里的一个备注,当匿名函数返回的函数是怎么赋值给f,这个赋值的底层是怎么一个实现)
解决办法如下实例所示,就是人为手动断开引用。
var f = (function(){var f,g;if(true){f=function g(){};}else{f=function g(){};} g=null; return f;})();