用途
1、用途之 保存功能 ---保存父函数局部变量
废话不说,直接上代码:
举例1:
html部分:
- 000
- 111
- 222
- 333
- 444
需求:点击li,弹出相应的索引值
window.onload=function(){
var oU=document.getElementById('ul1');
var aLi=oU.children;
for(var i=0;i<=aLi.length;i++){
console.log(i);
aLi[i].onclick=function(){
alert(i)
};
}
};
//这种写法是错误的,只能弹出aLi的长度5,
//因为i值取决于外部i值的变化,而他本身也有一个事件控制着
//关于这个问题最好的解释是通过词法环境来理解,,这样创建,i值此法环境是一样的
这时候,我们可以引入闭包,
每次给点击事件的i值一个不同的词法环境,
从而保存每次i值的变化,俗称保存现场
关于词法环境的理解方式原理,参考下图:
举例2:
需求:
将字符串中的一些特定字符 按顺序用数组中的元素替换,
例如:
var arr = ['c','f','h','o'];
var str = 'ab4de8g4ijklmn7';
替换后 str == 'abcdefghijklmno';
var arr = ['c','f','h','o'];
var str = 'ab4de8g4ijklmn1';
console.log(str);
var func = (function(){
// count变量会保存在闭包作用域内,
// 表示func被调用次数(即正在替换第几个字符)
var count = 0;
return function(){
return arr[count++];
}
})();
str = str.replace(/\d/g, func)
console.log(str);
这个问题,解决的关键是,记录替换的次数,
从而从数组中取字母进行替换,,
运用闭包的保存现场功能完美解决
举例3 --减少全局变量1
如果不使用闭包,使a自增,则需要将a变量放到外部
function foo2() {
var a = 0;
return function foo3() {
a++;
alert(a);
}
}
var foo4 = foo2();
foo4();
foo4();
举例4 --减少全局变量2
首先我们先来分析一段代码:
function fn1(){
var n=999;
nAdd=function(){
n+=1
}
return function fn2(){
alert(n);
}
}
var result=fn1();
result(); // 999
nAdd();
result(); // 1000
代码分析:
在这段代码中,result实际上就是闭包fn2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。
这证明了,函数fn1中的局部变量n一直保存在内存中,并没有在fn1调用后被自动清除。
为什么会这样呢?
原因就在于fn1是fn2的父函数,而fn2被赋给了一个全局变量,这导致fn2始终在内存中,而fn2的存在依赖于fn1,因此fn1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
使用闭包的注意点:
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
例如
var name='The window'; //外部变量值的改变,通过闭包改变了内部的值
var object={
name:'My object', //私有属性
fnName:function(){ //公共的方法
return function(){ //这里相当于闭包
return this.name;
}
}
};
console.log(object.fnName()());
2、用途之 封装
在做封装的时候,我们希望父函数的局部变量
不要暴露给调用方法的对象,
只把一些公共方法和私有属性暴露出去就好
需求:
1.暴露type类型和start, stop, getStatus方法
2.隐藏status,light对象状态
var Car = function(type){
var status = "stop",
light = "off"; //不希望暴露的变量
return {
type: type, //type属性
start: function(){ //start方法
status = "driving";
light = "on";
},
stop: function(){ //stop方法
status = "stop";
light = "off";
},
getStatus: function(){ //getStatus方法
console.log(type + " is " + status + " with light " + light);
}
}
}
var audi = new Car("audi");
audi.start();
audi.getStatus();
//结果:audi is driving with light on
audi.stop();
audi.getStatus();
//结果:audi is stop with light off
通过下图可知, var status = "stop", light = "off";
通过闭包并没有暴露,
我们只看到了,type
3、用途之 性能优化
举例1
// // //不使用闭包
function sum(i, j) {
var add = function(i, j){
return i+j;
}
return add(i, j)
}
//程序运行开始时间
var startTime = new Date();
for(var i = 0; i< 1000000; i++) {
sum(1,1);
}
//程序运行结束时间
var endTime = new Date();
console.log(endTime - startTime);
//结果:63ms
// // // 使用闭包
var sum = (function() {
var add = function(i, j){ //局部变量
return i+j;
}
return function(i,j) {
add(i, j); //闭包
}
})()
//程序运行开始时间
var startTime = new Date();
for(var i = 0; i< 1000000; i++) {
sum(1,1);
}
//程序运行结束时间
var endTime = new Date();
console.log(endTime - startTime);
//结果:8ms
代码分析:
这个例子中,父函数局部变量为函数,
不使用闭包的话,会重复定义函数,增加内存和时间
通过使用闭包,保存了局部变量,从而减少函数定义时间和内存消耗
举例2
普通递归函数跟使用闭包记录调用返回结果的递归函数调用次数对比
// // 普通递归函数--不使用闭包
var factorial = (function(){
var count = 0;
var fac = function(i){
count++;
if (i==0) {
console.log('调用次数:' + count);
return 1;
}
return i*factorial(i-1);
}
return fac;
})();
for(var i=0;i<=10;i++){
console.log(factorial(i));
}
//结果:最后一次调用次数为66
// // 使用闭包记录调用返回结果的递归函数 -- 记忆函数
var factorial = (function(){
var memo = [1];
var count = 0;
var fac = function(i){
count++;
var result = memo[i];
if(typeof result === 'number'){
console.log('调用次数:' + count);
return result;
}
result = i*fac(i-1);
memo[i] = result;
return result;
}
return fac;
})();
for(var i=0;i<=10;i++){
console.log(factorial(i));
}
//结果:最后一次调用次数为21