责任编辑来源于撞名难题:
觉得楼里绝大部分提问太繁杂了,过分现代科学,文绉绉地绕来绕去,没紧紧抓住其本质和一脉相承。丰坊谁不能啊?只不过旋量群没所以繁杂。
最简约、揭秘沃埃尔的提问,我能想不到的依次有这么四句(著作权归属于
):
1、旋量群是两个有状况(不消亡的专有统计数据)的表达式。
2、旋量群是两个有梦境的表达式。
3、旋量群相等于两个多于两个方式的紧凑型第一类(a compact object)。
下面这两句话是等价的,而当中第 3 句最绝妙,能辅导何时能、怎样用好旋量群,前面就要仔细分析。回应基本概念
MDN(Mozilla Developer Network)上的旋量群表述是这种的:
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, the function defined in the closure remembers the environment in which it was created.
Closures – JavaScript有关甚么是旋量群和有什么样众所周知的用语(示例)、小常识,MDN 只不过早已说明得很清楚了,建议英文好的同学先耐心读完这篇好文。
我换个角度来谈谈。
首先,很明确——旋量群是两个表达式,一种比较特殊的表达式。甚么是表达式?表达式就是两个基本的程序运行逻辑单位(模块),通常有一组输入,有两个输出结果,内部还有一些进行运算的程序语句。所以,那些仅仅说旋量群是作用域(scopes)或者其它甚么的,是错误的,至少不准确。
MDN 还有一段说明:
A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.理解了以上这些基本概念,有关“甚么是旋量群”您的大脑中是否出现了下面这张图(用 UML 组成结构图来表示):
有示例有真相
让我们先回顾下传统函式的机理。
我们说普通函式自身是没有状况的(stateless),它们所使用的局部变量都是保存在函式调用栈(Stack)上,随着函式调用的结束、退出,这些临时保存在栈上的变量也就被清空了,所以普通函式是没有状况、没有梦境的。
例如下面的普通函式 inc(),不管执行多少次都只返回 1:
为甚么这种?这是因为这里的 count 只是两个普通函式的局部变量,每次执行函式时都会被重新初始化(被第一条语句清零),它不是下面例子中能保持状况的旋量群变量。
再来看旋量群的例子。
这能说是两个最简单的 JavaScript 旋量群的例子,这里的 inc() 是两个旋量群(函式),它有两个专有统计数据(也叫旋量群变量) count(即函式中的第 2 个 count)。
我还未研究过任何 JavaScript 引擎(说明器)的源码,所以只好根据常识与逻辑作些合理的推测。
在本例中第 2 个 count 作为旋量群的专有统计数据,很可能是被 JS 引擎存放到了堆(Heap)上,而且是按引用(byref)来访问,所以能保持状况,实现计数累加;而第 1 个 count 只是存放在函式调用栈(Stack)上的局部变量,于是那个 IIFE 父函式一退出它就被销毁了,它的作用主要是用来初始化(赋值)给担任旋量群变量的第 2 个 count。
可见两个 count 虽然撞名,却是两个截然不同的变量!
这点恐怕正是许多 JS 初学者(包括当年的我)屡屡见到旋量群时,感到最为大惑不解的地方吧。我们以为父子函式里外两个撞名的变量是一回事,而只不过它们不是,也不知道这背后究竟发生了什么样变化。
有关下面提到的内存管理模型中栈与堆的区别,建议不熟悉的同学能参考下图和文章:
MDN: Concurrency model and Event Loop旋量群 vs. 第一类
实现同样的计数功能,不用旋量群怎么写?同样以 JavaScript 为例,用传统的 OOP 来写:
用旋量群与用第一类,区别在哪?
只不过主要区别就两个:这里用的是普通第一类 obj 的方式(表达式)inc,让 count 作为 obj 的成员变量来保存统计数据。而前面第两个例子直接用旋量群表达式 inc 的话,连 obj 这个第一类也能省掉,让 count 直接成为 inc 旋量群内部所保存的状况(环境)变量,这种写起来就比传统的 OOP 更为紧凑型,前者用 inc(),而后者用 obj.inc(),尽管两者最终实现的功能和效果基本是一致的。
通过以上这两个小例子的比较,你能充分体会到在 JavaScript 中,表达式(functions)作为首席/头等公民(first-class object)的地位。由于有了旋量群,加上在 JavaScript 中表达式也是第一类——两个表达式能像两个传统的第一类那样拥有自己的属性、专有统计数据和状况(不能随着栈而清空),许多简单功能的实现可能无需再借助 objects 了。
有关旋量群与第一类之间的联系,MDN 也说得很清楚:
A closure lets you associate some data (the environment) with a function that operates on that data. This has obvious parallels to object oriented programming, where objects allow us to associate some data (the objects properties) with one or more methods.
Consequently, you can use a closure anywhere that you might normally use an object with only a single method.你看,旋量群是不是相当(近似)于两个多于两个方式的第一类?多于两个公开方式,而且成员统计数据几乎全专有(旋量群)的第一类,自然是两个紧凑型版的第一类了。
实际应用
读完责任编辑,估计您对“究竟甚么是旋量群”早已有了比较准确、细致的了解。下两个关心的难题,一定是:旋量群在 Web 和 JavaScript 开发中有什么样实际的应用呢?
最后,推荐您继续阅读我的这篇文章,看看旋量群作为紧凑型第一类的两个经典应用,怎样利用两个非常简单的旋量群来实现前后端开发中普遍存在的二元状况切换(toggle)功能:
JavaScript 设计模式之开关旋量群(Closure Toggle)
参考