浅谈对js闭包的理解

一、什么是闭包?

   说起什么是闭包,大家各有各的说法,我个人认为《JavaScript高级程序设计》中说的:“闭包是指有权访问另一个函数作用域中的变量的函数(即闭包就是函数) ” 更为贴切且容易记住。有的定义中说闭包是函数,有的定义中说闭包是环境,我个人理解上,觉得函数理解要容易一些,下面是我摘录的对闭包的j几种定义。

  1. 闭包是指有权访问另一个函数作用域中的变量的函数(即闭包就是函数), 我能记住的定义
  2. 如果一个函数用到作用域之外的变量,那么这个变量和这个函数之间的环境就是闭包。
  3. 闭包是 【函数】和【函数内部能访问到的变量】(也叫环境)的总和;
  4. 闭包一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式中的一部分。

   也有人说, 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的活动对象才会被彻底释放


三、闭包的特点

  1. 函数嵌套函数;
  2. 函数内部可以引用外部的参数和变量;
  3. 参数和变量不会被垃圾回收机制回收

四、闭包的作用

  • 有权访问函数内部的变量
    最常见的就是函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。

  • 防止对全局作用域的污染
    如果我们把一些只用到一两次的变量放在全局作用域中,最后肯定是容易出错且不可维护的。通过闭包,我们可以实现在函数外部访问一个函数内的局部变量,而这些局部变量存储在函数中,则避免了污染。

举个栗子,说明闭包的好处和用处:

全局变量 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,实现了局部变量累加了

// 这说明,闭包会将变量始终保存在内存中,如果使用不当会增加内存消耗

五、闭包的缺点

  1. 滥用闭包会占用内存,造成内存泄漏。闭包将函数的活动对象维持在内存中,过渡使用闭包会导致内存占用过多;
  2. 闭包只能取得外部函数中任何一个变量的最后一个值,在使用循环且返回的函数中带有循环变量是会得到错误结果;
  3. 当返回的函数为匿名函数是,注意匿名函数中的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者所引用,那么这两个互相引用的对象也会被回收

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值