闭包定义
从严格意义上来说,构成闭包需要有对上下文词法作用域中变量的引用,并在外部函数执行完毕时,被引用的变量并不会被垃圾回收器回收。
函数可以访问函数内部的变量
function myFunction(){
var a=4;
return a;
}
函数也可以访问函数外部的变量
var a=4;
function myFunction(){
return a;
}
javascript查找变量即变量解析的过程,首先在当前定义的局部作用域中查找,如果未发现,就会查找上一层作用域。如下,变量scope被定义为outer函数的局部变量,而inner函数定义时即确定了其作用域,并引用了外部函数的局部变量scope,所以输出结果为20而不是110.
inner函数作为outer函数的执行结果被返回,当outer函数在执行完毕后,定义在其内部的变量scope并没有被返回,而是通过函数fn的执行被获取,这里的inner函数即形成了闭包。
function outer(){
var scope=10;
return function inner(){
scope+=10;
console.log(scope);
}
}
var scope=100;
var fn=outer();
fn(); //输出结果:20
fn(); //试着再次调用fn,此时输出结果为30
函数作为参数传递给自执行函数。
var scope=10;
function calculate(addend){console.log(scope+addend);}
(function(ca){
var scope=100;
ca(5); //输出结果:15
})(calculate);
一些例子
bar执行时,访问了foo内部的变量a和b,闭包产生。
//demo1
function foo(){
var a=20;
var b=30;
function bar(){
return a+b;
}
return bar;
}
var bar=foo();
bar();
foo中定义的函数在执行时访问了foo中的变量,闭包产生。
//demo2
function foo(){
var a=20;
var b=30;
function bar(){
return a+b;
}
bar();
}
foo();
内部函数_add被调用执行时,访问了add函数变量对象中的x,闭包产生。
//demo3
function add(x){
return function _add(y){
return x+y;
}
}
add(2)(3); //5
getName在执行时,它的this指向的是window对象,这时没有形成闭包的环境,因此没有闭包产生。
//demo4
var name="window";
var p={
name:"peter",
getName:function(){
return function(){
return this.name;
}
}
}
var getName=p.getName();
var _name=getName();
console.log(_name);
对demo4的改动1
//demo5
var name="window";
var p={
name:"peter",
getName:function(){
return function(){
return this.name;
}
}
}
var getName=p.getName;
//利用call使得this指向p对象
var _name=getName.call(p);
console.log(_name);
对demo4的改动2
//demo6
var name="window";
var p={
name:"peter",
getName:function(){
//利用变量保存的方式保证其访问的是p对象
var self=this;
return function(){
return self.name;
}
}
}
var getName=p.getName();
var _name=getName();
console.log(_name);
垃圾回收机制
- 当一个函数的执行上下文运行完毕之后,内部的所有内容都会失去引用而被垃圾回收机制回收。
- 闭包的本质就是在函数外部保持了内部变量的引用,因此闭包会阻止垃圾回收机制进行回收。
function f1(){
var n=999;
nAdd=function(){
n += 1;
}
return function f2(){
console.log(n);
}
}
var result=f1();
result(); //999
nAdd();
result(); //1000
nAdd与f2都访问了f1中的n,因此它们都与f1形成了闭包。变量n的引用被保留下来,因此nAdd每运行一次n加1。
应用闭包
循环、setTimeout与闭包
//运用闭包的知识,修改这段代码,使代码的执行结果为隔秒输出1,2,3,4,5
for(var i=1;i<=5;i++){
setTimeout(function timer(){
console.log(i);
},i*1000);
}
setTimeout的第二个参数访问的都是当前的i值,因此第二个i值分别是1,2,3,4,5。而第一个参数timer函数中虽然访问的是同一个i值,但是由于延迟的原因,当timer函数被setTimeout运行时,循环已经结束,即i变成了6。因此,这段代码的直接运行结果是隔秒输出6。
使用匿名函数更改。
//demo1
for(var i=1;i<=5;i++){
(function(i){
setTimeout(function timer(){
console.log(i);
},i*1000);
})(i);
}
//demo2
for(var i=1;i<=5;i++){
setTimerout((function(i){
return function timer(){
console.log(i);
}
})(i),i*1000);
}
单例模式与闭包
最简单的单例模式。
var per={
name:"jake",
age:20,
getName:function(){
return this.name;
},
getAge:function(){
return this.age;
}
}
上续单例模式的属性可以被外部更改,在很多场景中,我们期望对象能够有自己的私有方法与属性。
有私有方法与属性的单例模式。
var per=(function(){
var name="jake";
var age=20;
return{
getName:function(){
return name;
},
getAge:function(){
return age;
}
}
})();
per.getName();
私有变量的好处在于,外界对于私有变量能够进行什么样的操作是可以控制的。如上,我们已经利用闭包解决问题了,匿名函数中,当name被getName访问时,闭包产生,因此A中两个属性都会被保留下来。
调用时才初始化的单例模式
var per=(function(){
//定义一个变量,用来保存实例
var instance=null;
var name="jake";
var age=20;
//初始化方法
function initial(){
return{
getName:function(){return name;},
getAge:function(){return name;}
}
}
return{
getInstance:function(){
if(!instance){
instance=initial();
}
return instance;
}
}
})();
var p1=per.getInstance();
var p2=per.getInstance();
console.log(p1===p2); //true
我们在匿名函数中定义了一个instance变量用来保存实例。在getInstance方法中判断是否对它进行重新赋值,因此变量instance仅仅只在第一次调用getInstance方法时赋值了,符合单例模式的思路。
模块化与闭包
其他应用
利用闭包保存中间计算结果,实现计算结果的缓存。如下利用闭包函数计算斐波那契数列。
var count=0,
fib=(function(){
var arr=[0,1,1]; //前三位直接返回
return function(n){
count++;
var res=arr[n];
if(res){
return res;
}else{
arr[n]=fib(n-1)+fib(n-2);
return arr[n];
}
}
})();
fib(10);
滥用闭包的危害
滥用闭包会导致诸如内存泄漏的性能问题。