精华 JavaScript 闭包的理解
发布于 10 年前 作者 russj 18077 次浏览 最后一次编辑是 8 年前 来自 分享

关于闭包,阮一峰的这篇文章 写得比较清晰易懂。 不过需要注意的是闭包不是函数,而是函数的作用域。由于 JavaScript 的作用域不是由 block 符号 {} 来界定,而是函数,所以两者容易混淆。阮一峰说闭包是函数估计是帮助大家理解吧。

var name = "The Window";
 var object = {
     name : "My Object",
     getNameFunc : function(){
         var that = this;
         return function(){
             return that.name;
    	};
  	}
 };
 alert(object.getNameFunc()()); 

以上是他最后的思考题。因为闭包内的函数只能访问闭包内的变量,所以 this 必须要赋给 that 才能引用。

作用域

JavaScript 的闭包其实是一个作用域(scope),而这个作用域就是闭包内部的函数可以访问和修改变量的范围(注意因为闭包是外部函数的 {} 划定的作用域,所以提到函数时一般是指 {} 大括号内部申明的函数)。换句话说,闭包允许一个函数访问内部的所有变量和其他函数,只要这个函数是在这个闭包的作用域内申明的。

一个函数可以访问含有自身申明的闭包的原始作用域。

闭包作用域:

  • 函数的参数在函数的闭包作用域内。
  • 所有在函数自身作用域(函数的{})外的变量,甚至那些在函数申明之后申明的变量,都可以在函数内引用。

使用时注意闭包的开销,可能会影响性能。阮的文章有解释。

闭包的一个常用功能就是封装变量,类似其他语言的私有变量,来限制变量的作用域,不污染全局作用域。

我们也可以通过闭包内的函数来修改闭包内的变量值。所以,闭包不是一个简单的固定状态,而是可以随时改变的封装。

函数的偏应用(Partially Applying Function)

偏应用就是为一个多元函数(接受多个参数的函数)在调用前指定部分参数,从而在调用时可以省略这些参数。实际上,偏应用化一个函数就是返回一个预定义参数的新函数。 这种使用返回函数预先提供前几个参数的的技术叫做科里化(currying)。

立即被执行的函数

(function(){console.log("ran!");})();

这个函数会被立即执行,而不仅仅是定义。

在这个 <code>(…)()</code> 立即执行函数里,第一对括号只是分隔号,就像 <code>(3+2) * 4</code> 的括号功能一样。不过第二对括号是操作符,类似 <code>var sum = add(1,2);</code> 的括号。

this

在对象中,调用变量必须使用 this,不然会调用到对象外的全局同名变量。 在函数中,this 是函数所属的对象。如果使用 ‘use strict’, 函数里的 this 会变为 undefined。一般只有在初始函数(Constructor) 里才使用 this。

注意:闭包里没有 this 参数,因为每个函数调用有自己的 this。

在阮的思考题里,我们可以使用 bind 来达到目的。

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        }.bind(this);
	}
};
console.log(object.getNameFunc()());

‘use strict’

尽量在 js 文件开始处使用 ‘use strict’ 来减少陷入 JavaScript 里暗藏的坑。比如下面的代码就在 strict 模式下不允许。

function func(){
    this.a = 100;
    a  = 33;
};
this.a = 200;
console.log(this.a); // 非 strict 输出 200
func();
console.log(this.a); // 非 strict 输出 33

在闭包里使用循环

需要注意的一点: 闭包和循环如果同时使用的话有时会有问题,因为闭包内的变量是保存变化的,如果创建闭包之后再使用函数的话,循环里的 i 可能会一直是最后一个值(比如最大值)。

21 回复

那js中为什么有闭包呢?你有没有你考虑过?

@struCoder 我想闭包的作用之一是解决函数的作用域问题。 就是个可以少使用全局作用域,而光是本地作用域又不够,很多场景不够用,比如记住函数已经计算过的变量,这时候就得使用闭包了。

还有一个就是改变函数的上下文,因为函数传来传去,得和环境交互。

@russj 嗯,赞赞赞,可是我问的是为什么?你答的是其作用。 其实在js中,其独特的作用域引出了与之关联的作用域链, 而作用域链的作用就是:

保证对执行环境有权访问的变量或者函数进行有序地访问. 之所以有序的访问,是因为在作用域的最前端始终 是当前执行的代码所在的环境的变量对象,作用域的下一个变量对象来自其外部环境,如此反复直到全局对象. 而当一个函数中的变量或者函数有权访问另一个函数作用域中的变量或者函数时就产生了闭包了。:)

看不懂,有点高深

@struCoder 作用不就是为什么么?

我喜欢这句话 “而当一个函数中的变量或者函数有权访问另一个函数作用域中的变量或者函数时就产生了闭包了。” 可以帮助理解闭包。

@russj 一个函数中的变量或者函数有权访问另一个函数作用域中的变量或者函数,绕晕了,能给个例子么?

@chapgaga 熟悉这两个概念能帮助你理解闭包:执行上下文(context)和作用域(scope). 此处应该有谷歌 简单来说,当调用一个方法的时候会创建一个执行上下文(其它的还有全局上下文和使用eval方法也会创建执行上下文)。而作用域就是函数或者变量能被访问到的范围。 以下面的代码为例,两处调用bar的地方都能正确打印出结果‘3’,但是不同之处在于调用第一个bar的时候,此时context是foo创建的。所以bar自然能访问到x变量。而第二次调用mybar的时候,它的context变了,此时是在全局context上调用bar方法,而全局context是无法访问到x变量的,x的作用域只在foo创建的context内部,无法被外部访问。这就闭包的最简单例子了。

function foo() {
	var x = 1;
	function bar(y) {
		return x+y;
	}
	bar(2);//1+2=3
}
var mybar = foo();
mybar(2);//1+2=3

而lz给的例子中之所以用var that=this, 是因为在执行上下文创建的时候,还会绑定this。object.getNameFunc()(),此时绑定的this不是object,而是全局变量。和下面直接调用object.getNameFunc()方法是不同的。

var name = "The Window";
 var object = {
     name : "My Object",
     getNameFunc : function(){
      	return this.name;
    }
 };
 alert(object.getNameFunc()); //this 没变,还是object

希望有所帮助,好像越说越乱了。。。

this是不是用做指定作用域的

说的太复杂了

刚遇到的一个闭包使用 $(e).on(“click”,dismiss,function(){ var $this = $(this),$e = $("#e"); $e.unbind(“click”); $e.bind(“click”,function(){ return function(){ var text = $("#fomuDIVContent").text(); $this.data(“fomu”,text) } }($this)) });

在《javascript高级程序设计》书中,闭包章节讲的非常清楚,看过的所有解释中最浅显易懂的

PS LZ例子中的 getNameFunc : function(){ var that = this; return function(){ return that.name; }; } 返回的是一个匿名函数,匿名函数的this是会绑定到window的,所以需要用that指定回object。

@chapgaga

简单来说就是函数套函数,内部函数可以访问外部函数的作用域

@microcisco this 就是上下文 context,或者说就是所在的对象本身

@zhounanbin 为何要先unbind click,直接bind click会把前面的click事件覆盖的?

@microcisco

return function(){ return that.name; };

return回一个函数,这是搞啥呢?还没见过这种用法,呵呵

@chapgaga 累加 这和JQ的事件处理机制有关

@chapgaga 这种用法在函数式编程里应该挺普遍的。函数是JS里的第一公民

可以参考http://jsnoder.com/blog/javascript-functions/

闭包可以用在实现私有变量,以及需要局部变量常驻内存的场景。

@chapgaga 这貌似就是闭包吧,把内部变量that.name传出去。

其实我很纳闷,为什么一直流传JS简单……我觉得一旦涉及对象和原型,JS一点都不简单。至少网络上都没有一个能够很简明简单回答这些基本概念的帖子。于是这个简单的语言,大家一直在围绕着一些基础的概念在争论。。。。每个人都解释的很有道理的样子……但是到底是啥。。

js一点都不简单~~~

@Mark24Code 确实有点绕,特别你是学过其他 oop 的

https://cnodejs.org/topic/577298fd85e22178177ede44 这篇也写得不错

@struCoder 是因为函数保存了它定义时所处的词法环境的引用。

来自酷炫的 CNodeMD

回到顶部