一、什么是闭包?
说起什么是闭包,大家各有各的说法,我个人认为《JavaScript高级程序设计》中说的:“闭包是指有权访问另一个函数作用域中的变量的函数(即闭包就是函数) ” 更为贴切且容易记住。有的定义中说闭包是函数,有的定义中说闭包是环境,我个人理解上,觉得函数理解要容易一些,下面是我摘录的对闭包的j几种定义。
- 闭包是指有权访问另一个函数作用域中的变量的函数(即闭包就是函数), 我能记住的定义
- 如果一个函数用到作用域之外的变量,那么这个变量和这个函数之间的环境就是闭包。
- 闭包是 【函数】和【函数内部能访问到的变量】(也叫环境)的总和;
- 闭包一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式中的一部分。
也有人说, JavaScript中所有的function都是一个闭包 ,大家可以自己去细品,在此,我就不多说了。
举个栗子来说明下闭包:
function outer(){
var name = "闭包";
function inner(){
console.log( name );
}
return inner;
}
// 调用
var result = outer();
result(); // "闭包"
上述代码中包含了一个简单的闭包:outer函数的返回值是一个函数,即inner。 inner在outer的内部,理所当然能够访问到局部变量name,但当inner作为outer的返回值赋给outer外的全局变量result时,result这个全局作用域的变量也能访问到局部变量name,这就形成了闭包。
二、闭包的原理
每个函数都有自己的执行环境,当一个函数被执行时,其执行环境被推入到环境栈中,其活动对象(存储环境中定义的变量和函数)也被加入到作用域链中,一旦函数执行完毕,栈就会弹出该函数的执行环境,活动对象也被销毁。
对于上面的例子来说,outer执行完之后,会返回inner给result, outer的执行环境从环境栈弹出,控制权交给全局环境,outer的活动对象理应被销毁。 但此时inner已经存储在全局活动对象中了,同时inner需要访问name,所以outer的活动对象没有被销毁,即使result执行完毕,outer的活动对象依然存在作用域链中,直到result被销毁,
result = null
outer的活动对象才会被彻底释放 。
三、闭包的特点
- 函数嵌套函数;
- 函数内部可以引用外部的参数和变量;
- 参数和变量不会被垃圾回收机制回收
四、闭包的作用
-
有权访问函数内部的变量。
最常见的就是函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。 -
防止对全局作用域的污染。
如果我们把一些只用到一两次的变量放在全局作用域中,最后肯定是容易出错且不可维护的。通过闭包,我们可以实现在函数外部访问一个函数内的局部变量,而这些局部变量存储在函数中,则避免了污染。
举个栗子,说明闭包的好处和用处:
全局变量 count ++ 累加
var count = 0;
function getCount(){
count ++ ;
console.log( count );
}
getCount(); // 1
getCount(); // 2
局部变量 count ++ 不累加
function getCount(){
var count = 0;
count ++ ;
console.log( count );
}
getCount(); // 1
getCount(); // 1
// 据上述两个栗子,只是为了说明闭包的好处和用处
使用闭包,局部变量 count ++ 累加
function getCount(){
var count = 0;
return function () {
count ++ ;
console.log( count );
}
}
var plus = getCount(); // 函数赋值给变量
plus(); // 1, plus函数调用第一次,结果为1,相当于getCount()();
plus(); // 2, plus函数调用第二次,结果为2,实现了局部变量累加了
// 这说明,闭包会将变量始终保存在内存中,如果使用不当会增加内存消耗
五、闭包的缺点
- 滥用闭包会占用内存,造成内存泄漏。闭包将函数的活动对象维持在内存中,过渡使用闭包会导致内存占用过多;
- 闭包只能取得外部函数中任何一个变量的最后一个值,在使用循环且返回的函数中带有循环变量是会得到错误结果;
- 当返回的函数为匿名函数是,注意匿名函数中的this指的是window对象。
举个栗子,用循环说明闭包的缺点:
闭包的基本用法
在下面的代码中,loop函数运行后返回了一个函数,这个函数拥有一个自己私有的变量 i ,这个变量 i 不会因为loop函数运行结束了就被销毁。
返回的函数被赋值给result,这个result就是一个闭包,它的特征是拥有自己的私有成员(变量 i ),这个私有成员也可以是函数。
function closure(){
var i = 0;
return function () {
return ++i ;
}
}
var result = closure();
result(); // 1
result(); // 2
result(); // 3
result(); // 4
result(); // 5
带有循环的闭包
根据代码展示的结果,发现并没有实现循环展示 0,1,2,3,… 这样的效果。原因是:上述循环生成了10个闭包,但是, 他们的私有成员变量 i 是共有的,所以,当最后一个闭包函数生成后, i 的值已经等于 10 ,而前面所有的闭包中的 i 也就是 10 。
function loopClosure(){
var loopArray = [];
for (var i = 0; i < 10; i++) {
loopArray[i] = function() { return i; }
}
return loopArray;
}
var result = loopClosure();
result[0](); // 10
result[1](); // 10
result[2](); // 10
result[3](); // 10
result[4](); // 10
循环生成闭包并拥有真正的自己 i
要想循环生成的每个闭包都真正拥有自己的私有变量,那么 代码中的自执行函数就要用一个函数包裹生成的闭包函数,并为闭包函数提供一个独立的变量,这个时候闭包函数中的 i 是来自包裹它们的自执行函数的参数,而不是刚才的 loopArray 里面的那个 i ,这样他们才能各有各的 i 。
function loopClosure(){
var loopArray = [];
for (var i = 0; i < 10; i++) {
loopArray[i] = (function(i) {
return function () { return i; }
})(i); // 仔細对比这里的区别
}
return loopArray;
}
var result = loopClosure();
result[0](); // 0
result[1](); // 1
result[2](); // 2
六、js的垃圾回收
在JavaScript中,如果一个对象不再被引用,那么这个对象就会被GC(Garbage Collection)回收,计算机科学中一种自动释放不再被使用的内存空间的机制。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。