一开始接触闭包有些问题一直绕不过去,可了看其他的资料,也从网上查了查,下面是我总结的一些东西:
“官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
通俗的讲:就是函数a的内部函数b,被函数a外部的一个变量引用的时候,就创建了一个闭包。
(这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b)
function a(){ var i=0; function b(){ alert(++i); } return b; } var c = a(); c();
闭包的特性:
①.封闭性:外界无法访问闭包内部的数据,如果在闭包内声明变量,外界是无法访问的,除非闭包主动向外界提供访问接口;
②.持久性:一般的函数,调用完毕之后,系统自动注销函数,而对于闭包来说,在外部函数被调用之后,闭包结构依然保存在
系统中,闭包中的数据依然存在,从而实现对数据的持久使用。
优点:
① 减少全局变量。
② 减少传递函数的参数量
③ 封装;
缺点:
使用闭包会占有内存资源,过多的使用闭包会导致内存溢出等.
最简洁、直击要害的回答,我能想到的分别有这么三句
1、闭包是一个有状态(不消失的私有数据)的函数。
2、闭包是一个有记忆的函数。
3、闭包相当于一个只有一个方法的紧凑对象(a compact object)。
上面这三句话是等价的,而其中第 3 句最精妙,可以指导何时、如何用好闭包。
我换个角度来谈谈。
首先,很明确——闭包是一个函数,一种比较特殊的函数。什么是函数?函数就是一个基本的程序运行逻辑单位(模块),通常有一组输入,有一个输出结果,内部还有一些进行运算的程序语句。所以,那些仅仅说闭包是作用域(scopes)或者其它什么的,是错误的,至少不准确。
理解了以上这些概念,关于“什么是闭包”您的大脑中是否出现了下面这张图(用 UML 组成结构图
有实例有真相
让我们先回顾下传统函式的机理。
我们说普通函式自身是没有状态的(stateless),它们所使用的局部变量都是保存在函式调用栈(Stack)上,随着函式调用的结束、退出,这些临时保存在栈上的变量也就被清空了,所以普通函式是没有状态、没有记忆的。
例如下面的普通函式 inc(),不管执行多少次都只返回 1:
var inc = function () { var count = 0; return ++count; }; inc(); // return: 1 inc(); // return: 1
为什么这样?这是因为这里的 count 只是一个普通函式的局部变量,每次执行函式时都会被重新初始化(被第一条语句清零),它不是下面例子中可以保持状态的闭包变量。
再来看闭包的例子。
这可以说是一个最简单的 JavaScript 闭包的例子,这里的 inc() 是一个闭包(函式),它有一个私有数据(也叫闭包变量) count(即函式中的第 2 个 count)。
var inc = (function () { // 该函数体中的语句将被立即执行(IIFE) var count = 0; // 局部变量 count 初始化 return function () { // 父函式返回一个闭包(函式引用) return ++count; // 当父函式 return(即上一个 return)后,这里的 count 不再是父函式的局部变量,而是返回结果闭包中的一个闭包(环境)变量。 }; }) (); inc(); // return: 1 inc(); // return: 2
我还未研究过任何 JavaScript 引擎(解释器)的源码,所以只好根据常识与逻辑作些合理的推测。
在本例中第 2 个 count 作为闭包的私有数据,很可能是被 JS 引擎存放到了堆(Heap)上,而且是按引用(byref)来访问,所以可以保持状态,实现计数累加;而第 1 个 count 只是存放在函式调用栈(Stack)上的局部变量,于是那个 IIFE 父函式一退出它就被销毁了,它的作用主要是用来初始化(赋值)给担任闭包变量的第 2 个 count。
可见两个 count 虽然同名,却是两个截然不同的变量!
这点恐怕正是许多 JS 初学者(包括当年的我)屡屡见到闭包时,感到最为大惑不解的地方吧。我们以为父子函式里外两个同名的变量是一回事,而其实它们不是,也不知道这背后究竟发生了哪些变化。
闭包 vs. 对象
实现同样的计数功能,不用闭包怎么写?同样以 JavaScript 为例,用传统的 OOP 来写:
var obj = { count: 0, inc: function () { return ++this.count; } }; obj.inc(); // count: 1 obj.inc(); // count: 2
用闭包与用对象,区别在哪?
其实主要区别就一个:这里用的是普通对象 obj 的方法(函数)inc,让 count 作为 obj 的成员变量来保存数据。而前面第一个例子直接用闭包函数 inc 的话,连 obj 这个对象也可以省掉,让 count 直接成为 inc 闭包内部所保存的状态(环境)变量,这样写起来就比传统的 OOP 更为紧凑,前者用 inc(),而后者用 obj.inc(),尽管两者最终实现的功能和效果基本是一致的。
通过以上这两个小例子的比较,你可以充分体会到在 JavaScript 中,函数(functions)作为首席/头等公民(first-class object)的地位。由于有了闭包,加上在 JavaScript 中函数也是对象——一个函数可以像一个传统的对象那样拥有自己的属性、私有数据和状态(不会随着栈而清空),许多简单功能的实现可能无需再借助 objects 了。
最近有遇到一个这个的面试题,也是有关于闭包方面,很有意思,大家可以一起来看一下:
试题的要求是:遍历ul下的li,点击弹出li的索引
首先我们需要一个html结构
<div > <ul> <li>a</li> <li>a</li> <li>a</li> <li>a</li> <li>a</li> </ul> </div>
我们遍历ul 下所有的li 并添加点击事件,一般我们会在for循环里面添加点击事件,但是结果和我们所期盼不一样,那么是为什么呢????
var nodes = document.getElementsByTagName("li"); for( var i = 0; i<nodes.length; i++ ){ nodes[i].onclick = function(){ console.log(i); // 4 } }
接下来看看我们的js代码
var nodes = document.getElementsByTagName("li"); for( var i = 0; i<nodes.length; i++ ){ (function(index){ nodes[index].onclick = function(){ console.log(index); } })(i) }
我们实现了!!!
这样就是得来我们想要的效果点击相应的li得来相应的索引。
简单说一下实现的过程吧
(function () { /* code */ } ()); // 推荐使用这个
(function () { /* code */ })(); // 但是这个也是可以用的
这是我整理立调函数或自执行函数;
本质上我们是利用闭包的原理实现弹出的索引,我们立调函数传一个参数Index,也就是我们的索引i,在函数里面实现了闭包,
Index会一直保留在作用域块内,这样我们再点击的时候,会调用作用域名内保存的索引,从而实现我们需要的功能;
我们几个简单的例子:
function num(){ var i = 0; return function(){ console.log(i++); } } var counter = num(); console.log(counter()); // 0 console.log(counter()); // ??
var counter1 = (function(){ var i = 0; return { get:function(){ return i; }, set:function(val){ i = val; }, increment:function(){ return ++i; } } }()); console.log(counter1); console.log(counter1.get()); // ? console.log(counter1.set(3)); // ? console.log(counter1.increment()); // ? console.log(counter1.increment()); // ?
大家可以去思考一下答案哦!