土豆博客

JavaSciprt中的闭包

闭包(closure)就是能够读取其他函数内部变量的函数。在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成 定义在一个函数内部的函。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

如果你还不懂,没关系,我们一步步分析

# 一、变量的作用域

要理解闭包,首先必须理解Javascript特殊的变量作用域。

变量的作用域无非就是两种:全局变量和局部变量。Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

 var n=999;
 
 function f1(){
    console.log(n)  
 }
 
 f1(); // 999

而另一方面,在函数外部自然无法读取函数内的局部变量。

function f1(){
  var n=999;
}

console.log(n); //报错:Uncaught ReferenceError: n is not defined

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

function f1(){
  n=999;
}

f1();

console.log(n); // 999
# 二、从外部读取局部变量

正常情况下,我们并不能拿到局部作用域的变量。但是,我们可以使用变通的方式:在函数的内部,再定义一个函数。

function f1(){
  var n=999;
  function f2(){
     console.log(n); // 999
  }
}

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

function f1(){
  var n=999;
  function f2(){
    console.log(n)
  }
  return f2;
}
var result=f1();
result(); // 999

这就是闭包

让我们来看看这个函数做了什么:

  • 创建了一个函数 f1
  • 函数里面创建了一个变量 n 与函数 f2
  • 返回函数 f2

现在,我们就对闭包有了一个基本的概念:定义在一个函数内部的函数

# 三、闭包的典型使用

我们再来看一个例子

for(var i = 1; i <= 5; i++) {
    setTimeout(function() {
        console.log(i)
    }, 1000)
}

我们预想的结果是一秒后会循环输出1 2 3 4 5

但是运行上面的代码会输出 56,很明显,我们得到的结果却是 i 在循环之后的最终值。那么,为什么会是这样呢?

其实,由于作用域的工作方式,我们在定时器函数中访问到的i是共享到全局作用域的上的,它只有一个,就是最终循环结束的值

想让这个循环显示我们想要的结果也很简单,只需要这样:

for(var i = 1; i <= 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j)
        }, 1000)
    })(i)
}

使用一个立即执行函数将定时器函数包裹起来,在这个函数中定义一个变量 j,然后将 i 当做值传递进去。这样,在每次迭代的时候,变量 j 都会拥有 i 的一个拷贝,自然得到了我们想要的结果

同样的,这也是一个闭包的典型应用

# 四、闭包的用途

闭包有很多用途:

(1)读取函数内部的变量

(2)让这些变量始终保存在内存中

怎么来理解这句话呢?请看下面的代码。

 function f1() {
     var n = 999;
     nAdd = function () {
         n += 1
     }
     function f2() {
         console.log(n)
     }
     return f2;
 }

var result = f1();

result(); // 999
nAdd();  //调用f1函数中的nAdd方法
result(); // 1000

在这段代码中,result 实际上就是闭包 f2 函数。它一共运行了两次,第一次的值是 999 ,第二次的值是 1000 。这证明了,函数 f1 中的局部变量 n 一直保存在内存中,并没有在 f1 调用后被自动清除

(3)保护函数内的变量安全

现在我们要实现一个功能:统计一个函数调用的次数,需求很简单,直接开写

var count = 0;
function fn() {
	count++;
	console.log(count)
}
fn(); //1
fn(); //2
fn(); //3

但是由于count是可以被外界随意访问的,这时突然有人恶意捣乱

var count = 0;
function fn() {
	count++;
	console.log(count)
}
fn(); //1
fn(); //2
count = 0;
fn(); //1

按理说应该是调用3次,可是输出的次数却是1次,这样统计的次数就不正确了,也就是说这个数据就不安全了。接下来我们用闭包改写

function outer() {
var count = 0;
	function inner() {
		count++;
		console.log(count)
	}
	return inner;
}
var result = outer();
result();//1
result(); //2
//count = 0 错误,无法访问到count,因为此时count不再是全局变量
result(); //3

这样的话,闭包把count数据给保护起来了,外界就无法访问和修改count变量,也就保证了变量的安全性

(4)模块化代码(数据隐藏和封装)

对js代码进行模块化封装和构建,不仅仅有利于限制对代码的访问,还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分

var mytools = (function(){  //aaa是一个模块,私有变量是a,私有函数是bbb和ccc
    var a = 1;
    function bbb(){
        a++;
        alert(a);
    }
    function ccc(){
        a++;
        alert(a);
    }
    return {
        b:bbb,
        c:ccc
    }
})()  //函数表达式自执行,结果是return后面的对象
mytools.b(); //2
mytools.c(); //3
# 五、闭包引发的问题

闭包会使函数中的变量始终保存在内存中,不能被 JavaScript`的垃圾回收清理,很容易造成内存消耗过大,影响程序性能,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。

解决方法是,在退出函数之前,将不使用的局部变量全部删除。

code
top

扫码添加,一起进步

wechat-code

为了保障最佳预览体验,博客已不支持IE浏览器的访问,邀请您使用以下现代高级浏览器。

谷歌浏览器(推荐) 火狐浏览器

注:如果你使用的是360,QQ等双核浏览器,请开启极速模式