WEB应用之:JS 闭包

本文为学习总结,无益于读者,请忽略

一. 引子

闭包真的是一个很简单很简单的概念,离开了它,日子就不好过了。这里简单的总结下闭包。

二. 闭包

闭包与函数、作用域的概念密不可分,容易混淆。阅读本文前,务必掌握好作用域链的概念,参见我的另一篇文章:JS入门必经之路: this 指针+作用域链+原型链。内含详尽的例子与论述。

1. 先说说函数

函数的定义有三种方式,即,函数声明方式和函数表达式方式,以及使用Function构造函数的方式。

方式一.函数声明方式如:

function func(para1,para2)
{
	//函数体
}
方式二.函数表达式方式如:

var oFunc = function(){
	//函数体
};
方式三.使用Function构造函数方式,要求最后一个参数为函数体,如:

var oFunc = new Function(para1,para2,"alert('inside func body');");

其中,方式三不推荐,方式二定义的函数叫做匿名函数。

抛开定义的方式,我们这里关注的重点是:作用域链!没错,每次函数定义时,都会生成一个函数对象,函数对象的[[scope]]属性会指向函数的作用域链。而通过这条作用域链,这个函数可以访问到链上出现的变量。

2. 然后才是闭包

在一个函数里定义了另一个函数的时候,这个内部的函数构成一个闭包。所谓闭包,实际上也代表一种能力,就是内部函数通过作用域能够访问到外部作用域的变量。所以,认识闭包的关键点是先认识作用域。至于与匿名函数的混淆,从闭包的概念上来看,无论是否匿名,只要是一个在函数内定义的函数,都是闭包。

三. 闭包的作用

本文的重点是,研究闭包的作用。从定义上来看,闭包的作用无非是通过作用域引用外部作用域的变量。没错,作用域链是闭包生命力的根本所在。本文将列举一些实例来讲解为什么离不开闭包。

1. 通过闭包实现块作用域

JS里面的作用域只有两种,全局作用域和函数作用域,缺少块级作用域的概念。不过,拥有了函数这个法宝,已经足以模仿块级作用域了。闭包本身作为函数,满足两个条件:内部定义的变量不能被外部访问;内部通过作用域链可以使用外部作用域的变量。这不就是块级作用域的功能吗?!


2. 通过闭包访问只读变量

JS里面没有类、也没有私有变量、公有变量、受保护的变量等概念。不过,在函数中使用var定义变量,这个变量的表现就是私有的。问题是如何访问到私有变量。举例如下:

function Person(sInName)
{
	this.sName = sInName;
}
var oPerson = new Person('yao');
console.log(oPerson.sName);//yao
上面的代码中,包含了下述步骤:

1. 定义函数 Person , 函数 Person 是一个 Function 对象,此对象的[[scope]]属性指向了该函数的作用域链

2. 使用 new 操作符创建 Person 对象

1)创建一个 Person 对象

2)执行构造函数之前,创建了Person函数的变量对象,变量对象的 this 指针指向了步骤1)创建的 Person 对象;此外,变量对象中也记录了arguments和参数sInName

3)执行构造函数,遇到this.sName = sInName时,在该 Person 对象中加入变量 sName

4)返回创建的 Person 对象,于是 oPerson 指向了这个对象,该对象有 sName 属性,可以通过 oPerson.sName访问和改变


另一种做法:

function Person(sInName)
{
	var sName = sInName;
}
var oPerson = new Person('yao');
console.log(oPerson.sName);//undefined
上面的代码中,包含了下述步骤:

1. 定义函数 Person , 函数 Person 是一个 Function 对象,此对象的[[scope]]属性指向了该函数的作用域链

2. 使用 new 操作符创建 Person 对象

1)创建一个 Person 对象

2)执行构造函数之前,创建了Person函数的变量对象,变量对象的 this 指针指向了步骤1)创建的 Person 对象;此外,变量对象中也记录了arguments和参数sInName

3)执行构造函数,遇到 var sName = sInName时,在变量对象中加入变量 sName

4)返回创建的 Person 对象,于是 oPerson 指向了这个对象


这里出现的问题是此时的 sName 不是 oPerson 对象的属性,因而 oPerson.sName 是无法访问到 sName的。

为了实现只读不可写 sName , 可以这么做:

function Person(sInName)
{
	var sName = sInName;
	this.getName = function(){//通过这个闭包访问 sName
		return sName;
	};
}
var oPerson = new Person('yao');
console.log(oPerson.getName());
没错,oPerson 的一个属性是 getName ,它指向了一个匿名函数,而这个匿名函数通过作用域链可以访问到外围的 sName , 于是实现了只读。

3. 单例

单例模式指的是,一个类只有一个实例,并使具有全局性。这种需求就如同对系统时钟的需求一样,只需要一个。例子

假如我要取得一个全局唯一的 B 类的对象,可以这么写:

(function(){
	function B()
	{
	}
	oB = new B();//oB没有用var关键字,是全局变量
})();
也可以这么写:

var oB = (function(){
	function B()
	{
	}
	return new B();
})();


四. 闭包的坏处

闭包在执行过程中有可能通过引用外部作用域的变量,假如闭包长时间没执行完,就导致作用域链上的变量对象迟迟无法得到释放,消耗内存。

此外,运用闭包的过程中常常会不经意地形成循环引用,导致内存泄露。




































  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值