之前写过一篇关于闭包的文字,当时觉得对闭包已经掌握到目无全牛的程度——直到我看到这篇博文:大部分人都会做错的经典JS闭包面试题。
我觉得我应该重新审视闭包这个概念,以及一些之前了解不够细致的JavaScript细节。下面是我抽取出来的一些容易被忽略的概念以及我的思考。
1.JS中有几种函数
首先,在此之前需要了解的是,在JS中函数可以分为两种,具名函数(命名函数)和匿名函数。区分这两种函数的方法非常简单,可以通过输出 fn.name 来判断,有name的就是具名函数,没有name的就是匿名函数。
在IE下不能获取到去获取具名函数的name会返回undefined,所以下面给出兼容处理方式:
/**
* 获取指定函数的函数名称(用于兼容IE)
* @param {Function} fun 任意函数
*/
function getFunctionName(fun) {
if (fun.name !== undefined)
return fun.name;
var ret = fun.toString();
ret = ret.substr('function '.length);
ret = ret.substr(0, ret.indexOf('('));
return ret;
}
2.创建函数的几种方式
- 匿名函数表达式,形如:var fn = function () {}
- 声明函数,形如:function ghostlpx () {}
- 具名函数表达式,形如:var fn = function ghostlpx () {}
- Function构造函数
- 自执行函数
- 其它一些不入流的旁门左道,如eval
3.我的思考
- 关于函数表达式:具名函数表达式本质上也是函数表达式,只不过在外部调用的时候一定要用它的变量名fn,不能使用它的函数名,函数名只能在函数内部找得到。
- 对象内部的函数表达式:形如 var o = { fn : function () {...} } ,这本质上也是利用函数表达式的方式在创建函数。另外值得引起注意的就是,在该函数内部访问不到存放该函数的变量,因为作用域链是向上寻迹的。
- 关于“闭包”这个单词的翻译:准确吗?闭包 closure 应该取的是close的意思, close的本意, 除了闭合, 还有一个意思是贴近。而JS中的closure, 是取“贴近”的意思。设计者叫它closure, 是因为内嵌函数的作用域链贴近了父函数的作用域, 形成了作用域链。因为近, 所以可以用。
4.经典的闭包使用场景
今天刚好又回头看了看 ECMAScript 5 中新增的bind()方法,这个方法主要作用就是将函数绑定到某个对象上。bind()方法在 ECMAScript 3 中的实现恰好利用了闭包,所以拿出来分析一下。
if ( !Function.prototype.bind ) {
Function.prototype.bind = function ( o ) {
var boundArgs = arguments,
self = this;
//bind()方法的返回值是一个函数
return function () {
var args = [], i;
//将bind中传入的第二个实参以及传入新函数的所有实参都放到args数组中
for( i=1; i<boundArgs.length; i++ ) { args.push( boundArgs[i] ); }
for( i=0; i<arguments.length; i++ ) { args.push( arguments[i] ); }
//将self作为o的方法来调用,传入实参
return self.apply( o, args );
}
}
<span style="font-size:12px;">}</span>
bind()方法返回的函数是一个闭包,在这个闭包的外部函数中声明了self和boundArgs变量,这两个变量在闭包中用到。尽管定义闭包的内部函数已经从外部函数中返回,在闭包中照样可以正确返回这两个变量。