英语轻松读发新版了,欢迎下载、更新

请问最好的理解闭包最好的方法是什么?

定西Cheer 发布于 2016年05月26日 | 更新于 2016年05月30日
无人欣赏。

死在闭包上好久。。

共50条回复
tinyfool 回复于 2016年05月26日

你最好问的具体点

brambles 回复于 2016年05月26日 | 更新于 2016年05月30日

闭包很简单,就是在函数定义时把函数外部的环境打包进函数内,使得在函数体内可以访问到函数体外的环境(主要是访问函数体外的变量)

// 假设这里的环境为 (1)

function(){
  // 假设这里的环境为 (2)

  function(){
    // 假设这里的环境为 (3)

  };
};

在环境(1)中只能访问到环境(1)的变量,但是环境(2)中能同时访问到环境(1)(2)中的变量。环境(3)中能同时访问到环境(1)(2)(3)中的变量。

函数在定义时,把函数体外部的环境全部打包进了函数,使得函数体内可以访问到函数体定义外面的环境。这个函数+所打包的外部环境就称之为闭包。

tinyfool 回复于 2016年05月27日

2楼 @brambles 我也有点奇怪,有更复杂的情况么?

我一向觉得挺顺理成章的,为啥总有人觉得这个东西难呢?

599316527 回复于 2016年05月27日

函数里面的能访问外面的变量,外面的不能访问里面的变量

_yeshuai 回复于 2016年05月27日

是这样,我也是花了好长时间才内化的闭包。主要是因为原来只写过 java ,对 js 里面函数可以直接当参数传递这个事一时半会不习惯(java8 现在也已经支持行为化参数了)。闭包的真正用处是对于那写能够把函数当成参数传来传去的语言的。这个函数是必会有一些自己的东西(能访问到的变量等),这些东西随着代码的执行,一直会被这个函数访问到,这样,这个函数就形成了一个闭包。如果此时你还把这个函数当成参数传递,那么相应的这些东西(变量等)也就一起跟着过去了(能访问到)。

mistkafka 回复于 2016年05月27日

三点: 1. 编程中的闭包与数学中的闭包:两者概念不同。 2. 编程中的闭包是什么?本质上是一个只有子孙域可以访问得到的公共域。(域可以看作环境、变量;JS中只有function会创建域) 3. 为什么要用闭包?复用、模块化(命名空间)、更好的描述。 关于“复用、更好的描述”可以看这个例子:https://segmentfault.com/a/1190000004589338, 关于“模块化(命名空间)”其实很好理解,localEnv的命名不会覆盖(污染)topEnv嘛!

所有的语言特性都是为了更好的描述!

brambles 回复于 2016年05月27日

3楼 @tinyfool

我们常见的语言都是词法(静态)作用域,所以真没什么更复杂的情况。

动态作用域的语言这个情况就复杂了……比如 emacs-lisp 这种

xiaotie 回复于 2016年05月27日

不同语言下闭包有差别;一般不用理解,直接用即可,偶尔踩踩坑了,查搜索引擎为什么踩坑。实在蛋疼的话,选择一两个语言看看他们对闭包的实现机制。

定西Cheer 回复于 2016年05月27日

2楼 @brambles 这种我懂,可是实际运用的时候好乱 -。- 可能是我太笨了。。

定西Cheer 回复于 2016年05月27日

1楼 @tinyfool -。-因为我对这个也不懂 但是想要的答案不是二楼那种 但是我又不知道该怎么说。。大概我太小白了。。

定西Cheer 回复于 2016年05月27日

4楼 @599316527 道理我都懂 -。- 然而不会实际运用

xiaotie 回复于 2016年05月27日

11楼 @定西Cheer

类里面的成员会用吧?

class A{
  int a;
  void foo(){a = 5;}
}

把 class A 拿掉:

{
  int a;
  void foo(){a=5;}
}

就是闭包了。就这样用就行了。

具体实际工作中,如果发现一个闭包很绕,有两个解决方案:(1)搞懂该语言下闭包的具体机制;(2)把 class A 加上,把闭包转换为 class 。

我通常采用(2)这种方式来解决,没精力去研究(1)。

brambles 回复于 2016年05月27日

9楼 @定西Cheer

你去跑一下这个栗子试试,你就知道闭包该怎么用了。

var counterGenerator = function(){
  var count = 0;
  return function() {
    count = count + 1;
    return count;
  };
};

var counter1 = counterGenerator();
var counter2 = counterGenerator();

counter1(); // => 1
counter1(); // => 2
counter2(); // => 1
counter2(); // => 2

你的问题其实根本就不是不懂闭包是什么,而是不懂函数式语言如何进行抽象和封装,也就是闭包如何使用的问题。比如上面的例子,如果换成面对对象的写法你可能更容易理解。

var Counter = function(){
  this.count = 0;
  this.next = function(){
    this.count = this.count + 1;
    return this.count;
  };
};

var counter1 = new Counter();
var counter2 = new Counter();

counter1.next(); // => 1
counter1.next(); // => 2
counter2.next(); // => 1
counter2.next(); // => 2

其实上面两种写法干的事情是一模一样的,只是抽象方式不一样。一种是函数式的抽象方式,把数据封装在函数闭包内,另一种是面对对象式的抽象方式,把数据封装在对象上。就那么简单。

所以闭包要怎么用?你觉得你的某一个函数需要单独给它一个盒子来装东西的时候,就给他创建一个闭包。就比如上面那个计数器的例子,每一个计数器都需要一个单独的盒子来装 count 这个值,所以我就给他创建一个闭包。

// 每次执行 counterGenerator 函数都会创建了一个闭包 
// 也就是创建了最后返回的那个函数
// 以及最后返回的那个函数单独的盒子
var counterGenerator = function(){
  var count = 0; // 这里是下面那个函数单独的盒子
  return function() { // 这里是最后返回的函数
    count = count + 1;
    return count;
  };
};
定西Cheer 回复于 2016年05月27日

13楼 @brambles 你这么一说我大概知道我哪里不懂了 就是闭包+立即执行的时候 因为你举的栗子我都懂 - -。我好笨

brambles 回复于 2016年05月27日

12楼 @xiaotie

其实闭包没理解好本质上是对函数式语言的抽象方式不熟悉,其实作用跟生成一个类没什么太大区别。

定西Cheer 回复于 2016年05月27日

12楼 @xiaotie 不会啊 - - 是不是js里面没有类。。。

brambles 回复于 2016年05月27日

14楼 @定西Cheer

闭包+立即执行函数也是一样的啊。就是立即创建一个独立的盒子。

(function(){
  // 这里是独立的盒子
})();

在这个独立的盒子里面,你创建的变量或者函数不会对外部产生影响。因为外部不能直接访问你这个盒子内部的变量。这么做的主要目的是防止命名空间污染

xiaotie 回复于 2016年05月27日

16楼 @定西Cheer

没有类,你就这样用:

int a;
void foo(){a=5;}

这是不是就是最简单的代码?

加上大括号就是闭包了:

{
  int a;
  void foo(){a=5;}
}
xiaotie 回复于 2016年05月27日

15楼 @brambles 闭包就是语法糖

brambles 回复于 2016年05月27日

19楼 @xiaotie

首先请明确语言核心和语法糖的区别,否则可以谁说这世界上所有编程语言都是机器语言的语法糖而已。闭包是函数式语言必不可少的核心之一,没有了闭包会对语言有实质性的改变,所以闭包这并不是语法糖。而 JavaScript 的恰函数式占了一半,另一半是基于原型的面向对象,反倒是 class 才是 JavaScript 的 语法糖。

https://en.wikipedia.org/wiki/Syntactic_sugar

brambles 回复于 2016年05月27日

18楼 @xiaotie

加上大括号也不是闭包……

这叫作用域……

scope 和 closure 还是有挺大区别的……

你可以看一下我 13楼 的例子

xiaotie 回复于 2016年05月27日

20楼 @brambles

并不是只有函数语言才有闭包,并不是需要了解函数语言才能使用闭包。很多语言就是以语法糖形式实现的闭包,从使用角度来说,把它理解成语法糖就行了。我一般不管它们怎么实现的。

brambles 回复于 2016年05月27日

22楼 @xiaotie

  1. 首先这里讨论的是 JavaScript (见tag),并且 JavaScript 是真正的函数式语言,拥有真正的闭包。
  2. 你所说的那种 “闭包” 根本就不是闭包,甚至那个语言也不会把这种语法糖称之为闭包,那叫做 词法作用域 / 静态作用域。如果不能创建一个新函数作为函数的返回,那么称之为闭包简直就太可笑了。闭包=外部环境+函数。顺便 C++11 以上是具有真正的函数式特性的哦。
  3. 作为使用者确实可以不管啊,但是现在别人问的时候不能强行装懂告诉别人错的东西呀……
xiaotie 回复于 2016年05月27日

21楼 @brambles

如果像我这样理解,没必要定义“闭包”这个词。我觉得闭包这个定义是无用定义,就是弄出来加大心智负担的玩意。

所以从实用角度:

int a;
void foo(){ a = 1; }

就当它是程序的语法糖,让你可以尽可能多的地方,能够像上面那样很爽的写代码,在函数里访问外部(作用域受限)的变量。没有闭包的语言,像上面那样写,在很多场景下编译没法通过。

这样理解,就可以消灭“闭包”这个概念,学它干嘛呢!

xiaotie 回复于 2016年05月27日

23楼 @brambles

你这样说我无话可说,我是实用主义者,在工作中就是像上面我阐述的那样处理各个语言里的“闭包”的,不管它是真的“闭包”还是它宣称的“闭包”,这种处理不需要理解什么,能处理绝大多数场景。

一家之言,供作者参考。

brambles 回复于 2016年05月27日

25楼 @xiaotie

这一切的原因在于,你写得不是函数式语言,仅此而已。其实我一样可以说c++的模版,类,宏都是弄出来加大心智负担的玩意呢。其实那么复杂的东西有什么意义呢?也不过就是多复制粘贴几次就能解决的问题吗?

闭包也是一样的。现在那么多语言愿意把函数式特性加进来,真不是因为大家都吃饱了撑着。能让势利保守到如 Java 这种语言都愿意把函数式特性加进 Java8 里面难道不应该只有一个原因吗?那就是实用,并且工业界认可的实用。

学院派的奇技淫巧,以及还没有得到工业界认可的东西都还老老实实呆在 Haskell / Lisp 里面呢……

xiaotie 回复于 2016年05月27日

26楼 @brambles

我工作中用到闭包的地方主要是as3和C# lambda表达式。嘿嘿,用的就是我所阐述的这些,超出这个范围我都是把它转换成类。其实你在工作中会发现,闭包最有用的就是我说的这些,别的语言把闭包搞进来,福利也就是这些。我不主张你举例子里的那个用法的,那个在实际工作中是我尽量避免的地方,那种情况下,用类更清晰。当然,js这种。。。写的不多,嗯,俺以前用haxe,现在用typescript,同样,还是上类,嘿嘿。

这是大量项目后取舍得到的均衡点。这种搞法,根本不用知道闭包是什么,直接用就行了,学习的东西特少,代码又清晰。

至于C++,我也觉得c++的模版,类,宏都是弄出来加大心智负担的玩意,所以我现在基本不写C++代码了。重点不是你觉得怎么样?而是,你觉得怎么样而去行动,并且达到了预定的效果。

定西Cheer 回复于 2016年05月27日

26楼 @brambles -.- 你说的我都看不懂。。。

brambles 回复于 2016年05月27日

27楼 @xiaotie

反正实用的话,用自己最熟悉的东西就好啦~

xiaotie 回复于 2016年05月27日

28楼 @定西Cheer

(1)

闭包这个词是从数学概念发展过来的。在数学上,比如说,区间(0,1)上有一个数列:

0.9,0.99,0.999,0.9999,0.99999 ……

这个数列中的每一个元素都在(0,1)内,但是,这个数列的极限,却并不在(0,1)内,它的极限是1。这种情况下引入的闭包定义,(0,1)的闭包是[0,1],也就是,(0,1)里的每个收敛数列的极限都在[0,1]内。

(2)

这个概念直接引入程序设计中,是这样的:

一个函数,比如说,这样的一个函数:

int foo(){ return 2;  }

这个函数,无论你怎么折腾它,怎么把它复制到各个地方,它都能跑。

但是下面这个函数:

int a = 5;
int foo(){ return a + 1; }

单就这个函数本身来说:

int foo() { return a + 1; }

是个不完整的函数,单独它不能跑,它依赖于外部的一个变量 a。就好比,在(0,1)内,0.9,0.99,0.999 ... 这个数列的极限不存在一样,没有 a,这个函数就是废的。再好比,你把一个男人扔到荒岛上,他的生殖功能(函数)就是废的。这种情况下,它要成立,就需要依赖外部的一个东东,它的闭包,就包括这个函数自身,以及,它依赖的最小集合。

(3)

定义到这里,你就会发现,闭包无处不在。只要是函数就是闭包,甚至万事万物都有闭包。也就是说:一个东西起作用的最小环境就是它的闭包。这样的定义,看起来没用,其实蛮有用的,在软件设计,对指导解耦,还有正交化分解非常有用。

我觉得这个定义,才是闭包最本质的概念。

(4)

这个定义,非常本质,但是太宽泛。因为:

int a = 5;
int foo() { return a + 1; }

这个搞法太普遍了,将它称为闭包没什么意义。现在很多语言都提供了函数里面套函数的机制,比如:

void foo()
{
  int a = 5;
  int foo2() { return a + 1; }
}

通常特指这种场景下,函数里的函数能起作用的最小环境是闭包。这是业内对闭包采用最广的定义。但实际上,你会发现,它其实就是(2)的一个特殊的例子而已。

(5)

而 js 里的狭义的闭包,也就是 bramble 上面说的闭包,实际上强调的是,把函数本身作为对象传来传去时,自身会携带它所依赖的环境(自己携带干粮,自干五的意思),这个是“闭包”在传递层面的实现。

你不实现,传出来也没用啊!跑不了啊!

这应该是函数作为闭包的封装和传递机制,在各个语言上实现是不一样的。自干五的函数就是这个意义上的闭包。

(6)

我们回过头来,你会发现,各个主流语言的函数、方法,都有传递环境的方法。面向对象中,一个Object里的方法,自身就和这个Object绑定的。但是,并不是所有的Object都是它的方法的闭包,因为里面有很多冗余的东东,不能算最小环境,Object应该是它的方法闭包的超集。如果你手动写了一个Object,里面有一个方法A,并且没有其它方法,和冗余的字段,这个Object就是方法A的闭包。

我为什么说闭包是语法糖?

你要传递函数,需要连同它的环境一起传递,最经济的方法就是传递最小环境。你手动打包太麻烦了,编译器或者是语言,会自动帮你把环境打包,只打包最小环境,不需要的不打包。

brambles 回复于 2016年05月27日

30楼 @xiaotie

然而说了那么多,全是你的臆想啊。而且程序语言中的闭包概念比冯诺伊曼体系的时间还长,不能随随便便胡说八道吧……

1945 年冯诺伊曼才把奠定现代计算机基础的论文写出来。但是函数式语言的闭包概念早在 1936 年 Church 的 Lambda Calculus 就早已经出现了。

所以拿现代高级语言来说事纯属胡说八道。

xiaotie 回复于 2016年05月27日

31楼 @brambles

我是语用派,不是语法派。你就说我这套逻辑体系哪里有问题吧?

逻辑自洽,又有用就行了!

中医五行几千年了,有毛用?

我能说他们说的闭包都是错的吗?就好比过去几十年的主流观点是神经网络是废物一样。

Palm 回复于 2016年05月27日

我理解的闭包就是 函数a 的内部函数b 被函数a环境之外的变量引用的时候就创建了一个闭包。 例如:

function a() {

 var s = 0 ; 
return function() {
        return (++s) ;
  } ;
}

var result = a () ; //这时候 result 指向了函数a内部的匿名函数

使用地方可以看下面的例子,可能不太恰当,但是应该能说明问题:

function addstr (str0) {

  return function(str1) {

       return function(str2) {
              return str0 + ' ' + str1 + ' ' + str2 ;
           } ;
   } ;
} 

console.log(addstr('北京')('北京')('朝阳')) ;

不过闭包可以使用对象编程来替代, 不仅解决了晦涩的语法(因为函数调用了还驻存在内存里这块我们不理解的情况下是不知道的),也比较好维护代码和简单易懂, 还有网络上都说闭包的内存回收是一个问题。所以,我是不太建议使用闭包的,也可能是我也不也不太会使用它。

//不太会用ourcoders的markdown编辑 .. 调整了半天也没有调整好 无语 ~~~~

//终于调整好了, 原来有自定义代码快。。。。。

//另外可以参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures#Practical_closures

xiaotie 回复于 2016年05月27日

31楼 @brambles 我想起了二十八个半布尔什维克,嘿嘿

定西Cheer 回复于 2016年05月27日

-。- 我的天。。看到现在我都蒙圈了@brambles @xiaotie 反正你俩说的我都不懂。。。。。。。

定西Cheer 回复于 2016年05月27日

33楼 @Palm 啊哈哈 谢谢你的回答

定西Cheer 回复于 2016年05月27日

另:语法糖是什么鬼 - -。

xiaotie 回复于 2016年05月27日

37楼 @定西Cheer

让你写代码爽的东西

brambles 回复于 2016年05月27日

32楼 @xiaotie

思而不学则怠,放在任何领域都受用。不懂这个领域没关系,别光自己瞎想,多看点书。程序语言是一门挺深的学科,不是随便会用两三个语言就能指手画脚的。

就像我不可能说你们搞算法不就是排个序的嘛,一样的道理

brambles 回复于 2016年05月27日

37楼 @定西Cheer

我自己造了个语言玩: https://github.com/bramblex/Smooth

xiaotie 回复于 2016年05月27日

39楼 @brambles

俺是实战派,不是学术派。作为程序语言的十几年的“用户”,不能对这个东东指手画脚,这才叫搞笑呢!你看见哪个行业产品的资深用户不能对厂家指手画脚?厂家通常都特欢迎这样的人。

俺搞算法反而没几年,五年的时间,至今排序我不会,二叉树反转俺也不会。猜想、验证、反馈、观察、改进的过程,战绩赫赫。哪能学究派搞法?等有空了扯扯算法设计

brambles 回复于 2016年05月27日

41楼 @xiaotie

不说那么多废话了,其实你爱怎么理解都没问题,爱怎么用也没问题。关键是不要胡扯……

就此打住

xiaotie 回复于 2016年05月27日

算了,不扯了,这段删除。

brambles 回复于 2016年05月27日

43楼 @xiaotie

你可以说我学究,我傻逼。但是你要明白,现在主流语言没有一个不是聚集了一大堆工程界的顶尖人才,人家全都比你我牛逼。人家辛辛苦苦费劲心思搞出来的东西,你学都没学明白了就认定这一批在工程界最顶级人才设计出来的东西没用?难道你觉得这些顶尖的工程人才全是傻逼吗?人家费劲心思就给搞一个没什么卵用的语法糖?你觉得人家是有多闲?要知道给语言加函数式特性是没有办法不动语言核心的。我之所以喜欢写编译到JavaScript的语言,是因为只要一旦编译到其他指令式语言,我就需要带着厚厚的runtime。

你用了十几年的编程语言,说句不好听的,能覆盖几个编程语言范式?至少函数式是连门槛都没摸着过吧。

xiaotie 回复于 2016年05月27日

这段删除。等做出成绩来了再说。

brambles 回复于 2016年05月27日

45楼 @xiaotie

对,你牛逼。但是不得不事,程序语言领域,在我眼里你还是小白……

这是不同的领域。没有人能精通所有领域,那叫做神

xiaotie 回复于 2016年05月27日

46楼 @brambles

but who care?

icediv 回复于 2016年05月28日

46楼 @brambles 顶你

我以前也和你一样,但突然有一天就开悟了,我发现我是真的没办法让石头开花的

有些气真的没必要,无视就好了

brambles 回复于 2016年05月28日

48楼 @icediv

我知道很多人装逼被打脸还硬要死撑着其实不过就是要挣那个面子而已。人家说 “who cares” 的实际已经非常 care 了,只不过死不肯认错而已。

所以说我也太年轻了。1. 我自己也同样犯了这个病,同样在争面子企图说服对方(虽然对方已经处于哪种 “我不听我不听” 的鸵鸟状态)。2. 我真的没必要图一时之快强行正面打人家脸,毕竟被别人用力打脸了谁都不舒服。

我自己能做到被人打脸了可以放低姿态认错,谦虚学习抱大腿,毕竟有能力打我脸的人肯定在某方面能力比我强。但是我不能要求人人都像我这么没脸没皮

定西Cheer 回复于 2016年05月30日

49楼 @brambles -。-但是在我眼里你们都是大神啊~~淡定淡定~~~

本帖有50个回复,因为您没有注册或者登录本站,所以,只能看到本帖的10条回复。如果想看到全部回复,请注册或者登录本站。

登录 或者 注册
[顶 楼]
|
|
[底 楼]
|
|
[首 页]