本文共 2913 字,大约阅读时间需要 9 分钟。
三、突破作用域链
1.未使用闭包时
默认情况下上一级作用域(以下称父作用域)中不能访问下一级作用域(以下称子作用域)中声明的变量,这一点前面已有例证。
2.使用闭包突破作用域链
如果确实需要在父作用域中访问子作用域中声明的变量,可以通过下面这样的方法:
//包含特殊数据的一个函数 function variableFunction() {
var variableForGloabl = "需要在上一级作用域中使用的变量";
//声明一个子函数,这个函数是可以访问到variableForGloabl的 function getVariable() { return variableForGloabl; }
//将子函数的引用返回,这一点很关键 return getVariable;
}
//获取getVariable()函数的引用 var funRef = variableFunction();
//调用有权访问局部变量的getVariable()函数获取数据 var data = funRef(); console.log("data="+data);
//可以多次调用获取 data = funRef(); console.log("data="+data); |
执行结果: data=需要在上一级作用域中使用的变量 data=需要在上一级作用域中使用的变量 |
3.闭包
其实上例中有权访问局部变量的getVariable()函数就是闭包。它所起的作用是将局部变量的作用域延伸到了全局范围——当然也可以说是从子作用域延伸到了父作用域。
这里有几个问题需要说明:
①返回函数的引用
实现闭包的过程中返回函数的引用是非常关键的一步,如果不是返回函数的引用就与改变作用域范围无关了。因为如果不返回函数的引用则可能直接返回变量值本身或闭包函数的执行结果,这样一来局部变量的作用域并不会被改变,我们仅仅是将它的值返回了,父函数执行完成后,局部变量就随之被垃圾回收机制释放掉了,对原本的作用域没有任何影响。
②缓存效果
局部变量被闭包函数返回后,即使父函数执行完毕,局部变量仍然会驻留在内存中,这是闭包技术中的一个非常鲜明的特点。
首先我们来证实这一点:
//声明包含闭包的函数keepVariable() function keepVariable() { //number是要保持的数据 var number = 1; //打印初始值 console.log("init number="+number); //返回闭包函数 return function(){ //由于闭包函数的引用被返回给了上一级作用域,随时会被调用执行,所以number要始终保持在内存中,直到父作用域也被释放 number++; console.log("number="+number); }; }
var closure = keepVariable(); closure(); closure(); |
执行结果: init number=1 number=2 number=3 |
四、实例
下面我们来看几个使用闭包技术的具体例子:
1.数据封装
在Java中,成员变量往往被设置为私有,提供对应的public权限的getXxx()和setXxx()方法与外界交互。而JavaScript中没有类的概念,也没有权限的概念,默认情况下对象的属性是可以直接读写的。
①直接读写对象属性
var person = {personName:"Tom"}; console.log("person name="+person.personName);
person.personName = "Jerry"; console.log("person name="+person.personName); |
执行结果: person name=Tom person name=Jerry |
②封装数据
在JavaScript中,我们可以借助作用域链封装数据,借助闭包提供getXxx()和setXxx()方法。
function Person(){ var personName = "init-value"; return { getPersonName : function(){ return personName; }, setPersonName : function(perName){ personName = perName; } }; }
var personObj = Person(); personObj.setPersonName("Tom"); console.log("Person's name="+personObj.getPersonName()); |
执行结果: Person's name=Tom |
看到这里可能大家会有疑问:这里返回的是一个对象并不是子函数啊?其实闭包的“官方解释”中并没有规定闭包必须是一个函数,所有封装局部变量并将作用域延伸到上一级都可以叫闭包。
2.数据缓存
在较为复杂的JavaScript应用中,有些数据的生成和获取比较耗时。那如果每次用到数据都重新获取显示是效率十分低下的。对于获取不易的数据,将其缓存起来往往是一个提升性能的重要手段。
function cacheInit() { //缓存数据的容器对象 var cache = {};
//闭包方法 return function(){
//首先尝试从缓存中读取数据 if('dataCached' in cache) { console.log("--->读取缓存"); //如果数据已在缓存中则直接返回 return cache['dataCached']; }
console.log("--->读取缓存失败,下面主动生成数据");
//执行一个非常耗时的创建数据的函数,返回数据 function createData() { console.log("--->生成数据"); dataCached = "Heavy Data"; //将数据保存到缓存中 console.log("--->将数据保存到缓存中"); cache['dataCached'] = dataCached;
return dataCached; }
//返回数据 return createData(); }; }
var getData = cacheInit(); var heavyData = getData(); console.log("第一次获取数据:"+heavyData);
heavyData = getData(); console.log("第二次获取数据:"+heavyData); |
执行结果: --->读取缓存失败,下面主动生成数据 --->生成数据 --->将数据保存到缓存中 第一次获取数据:Heavy Data --->读取缓存 第二次获取数据:Heavy Data |
本教程由尚硅谷教育大数据研究院出品,如需转载请注明来源。