最近打算换工作,所以参加了几次面试(国内比较知名的几家互联网公司)。在面试的过程中每当被问起闭包,我都会说闭包是作用域的问题?令人惊讶的是几乎无一例外的当我提到作用域时我都被打断,并提醒我好好的找一本javascript的书籍看看。而当我忍不住去问面试官对于闭包你是怎么理解的?我得到的大多是回答都是通过返回一个函数,然后通过这个函数来访问局部变量(私有变量),有的还会扯上声明提前,this指向等。听到这些,心理默默滴血,没错,我只是菜鸟。
看到有这么多人不理解闭包,所以我不得不再次的提及,如果有误,欢迎指正。
闭包只是为了实现词法作用域而用到的一种数据结构而已
先从阮一峰09年写的一篇关于闭包的文章开始(原文地址)文中说"可以把闭包理解为就是能够读取其他函数内部变量的函数,因为js中,只有函数内部的子函数才能读取局部变量,因此也可以把闭包定义在一个函数内部的函数。所以闭包本质就是将函数内部和函数外部连接起来的一座桥梁"。
毕竟不是专业学习js的,也不是程序语言方面的专家,在这里就不去计较了,下文会给出更完整的闭包说明。(PS:个人还是比较欣赏阮一峰写的文章的,能将一些概念讲得通俗易懂)
最后还留下了一个思考题
-
示例01:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());
-
示例02:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()());
看完这道题,就希望大家将this和闭包分开,不要给自己找麻烦?
在开始说闭包之前,需要理解好以下的概念:
词法作用域(静态作用域)
函数上下文
之前简单的提过,词法作用域简单的理解就是函数的上下文是在声明是确定的,而不是在调用时确定的。这里不想对这两个名词作过多的解释,我们知道js/ruby/python等主流的语言都是词法作用域就好,因为与之相对的动态作用域有许多的问题,所以现在的语言基本都是词法作用域的。
函数上下文就简单的理解为函数执行的环境好了,在这个环境中保存了函数执行所需的变量。
JavaScript权威指南第六版关于闭包的说明
JavaScript采用词法作用域,也就是说函数的执行依赖于变量的作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。为了实现词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为"闭包"。
当定义一个函数时,它实际上保存了一个作用域链。当调用这个函数时,它创建
一个新的对象来存储它的局部变量,并将这个对象添加到保存的作用域链上。
(闭包可以简单的理解为函数用来存储它的局部变量的对象,这个对象我们来说是不可见的,是js解释器实现的过程中才会用到的。每一个函数都会有这样的一个对象,作用域链则是这些对象之间的关系。)
"闭包"这个术语的来源:指函数变量可以被隐藏于作用域链之内,因此看起来是函数将变量"包裹"了起来。
看文字可能会比较绕,下面是书中的一个例子:
var scope = "global scope"; /*全局变量*/
function checkscope(){
var scope = "local scope"; /*局部变量*/
function f(){return scope;}
return f();
}
checkscope(); /*=>"local scope"*/
var scope = "global scope"; /*全局变量*/
function checkscope(){
var scope = "local scope"; /*局部变量*/
function f(){return scope;}
return f;
}
checkscope()(); /*=>"local scope"*/
checkscope最后的返回值都是一样的,即"local scope"。
上面两个例子的不同之处就是函数f执行的地方不同,一个在checkscope这个作用域里调用的(每一个函数都是一个作用域),一个在全局作用域里调用的。回忆之前说的,函数调用时的上下文或者说作用域是在声明时确定的,所以与调用的位置无关,即f函数的调用位置虽然不同,调用的环境虽然不同,但最终的结果都是一样的。
说到这里,闭包就说得差不多了,回过头来看一下常规的对于闭包的理解:
通过返回函数的形式取得函数的局部变量。
这种说法本身没有错,但它只是闭包的一种表现形式,
比如将上例进行下更改:
var scope = "global scope"; /*全局变量*/
function checkscope(fn){
var scope = "local scope"; /*局部变量*/
function f(){return scope;}
fn(f);
}
checkscope(function(func){
var scope = "func scope";
return func();
}); /*=>"local scope"*/
改成类似回调的执行方式,结果还是一样的,注意结果并不是func scope,但是并没有返回f函数这一说,难道这就不是闭包了吗?(当然这里有点扣字眼)
说说this
想起最开始时的那个思考题了吗?与闭包就没什么关系(注:任何一个函数其实都用到了闭包,但我们暂且考虑两层以及两层以上的嵌套情况,未嵌套情况下因为使用的都是全局作用域,结果应该是很直观的)。this一般用来代表函数的调用对象,它和上下文对象并不是同一个,上下文对象对我们来说是不可见的,除了全局作用域。
-
示例01:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); /*The Window*/
-
示例02:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; var that = {name: 'xiaofu'}; /*干扰项*/ alert(object.getNameFunc()()); /*My Object*/
我们再来看一下题目,示例01输出的是"The Window",示例02输出的是"My Object"。
说明:
示例01最终执行的是 function(){return this.name},因为没有显示指明调用对象,所以其this执行全局对象。
示例02先调用object.getNameFunc(),因为显示的指定了调用对象,所以内部的this是object(注:这里说的是this指向的问题,还没有说闭包),接着执行function (){return that.name},这个函数在getNameFunc这个函数作用域中声明的,所以它调用的时候使用的是这个作用域,即得到var that = this;的这个that;而不是外面的that。
作用域链不等同于原型链
真不知道这两个不相关的东西怎么会被等同起来看待,以后谁告诉我它们是同一个东西,我就想问了,ruby、python这种没有原型概念的语言难道就没有作用域链了吗?
更有甚者将变量声明提升和闭包混在一起,也是醉了。
总结
闭包:函数执行时变量的获取从声明的作用域处去获取(注意链式关系,当前没有就往父级找,知道全局作用域)
this:显示指定调用者则this就指向谁,如未指定则为全局对象
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。