闭包的形成和作用域以及生命周期有关
闭包的定义
● 当某个函数调用时会创建一个执行环境以及作用域链,然后根据arguments和其它命名参数初始化形成活动对象。
● 在外部函数调用结束后,其执行环境与作用域链被销毁,但是其活动对象保存在了闭包之中,最后在闭包函数调用结束后才销毁。
● 简单的说,闭包就是能够读取其他函数内部变量的函数。在js中,闭包是指有权访问另一个函数作用域中的变量的函数。
如何使用
A函数内部的B函数调用了A中的变量,将A函数内部的B函数作为A函数的返回值返回;
在一个函数内部又定义了一个闭包;
为什么要用它?有什么作用
匿名自执行函数;
更优雅,更简洁的表达出代码;
在某些方面提升代码的执行效率;
实现封装;
实现面向对象中的对象;
缓存
最大的用处有两个:
1)可以读取其他函数内部的变量
2)让这些变量的 值始终保存在内存中(闭包使得Javascript的垃圾回收机制GC不会收回外部函数所占用的资源,因为外部函数的内部函数的执行需要依赖外部函数中的变量。 )
看下面这个例子:
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
使用闭包的注意事项
(1) 问题:由于闭包会使得函数中的变量都被保存在内存中,内存消耗大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能会造成内存泄露。
解决方法:在退出函数之前,将不使用的局部变量全部删掉
(2) 问题:闭包会在父函数外部,改变父函数内部变量的值。
如果把父函数当做对象使用,把闭包当做公用方法,把内部变量当做私有属性,此时不要随便改变父函数内部变量的值。
//----代码一
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){ //js中,函数就是作用域
return this.name; // 闭包 + this,造成的问题,函数中的this不能访问到作用域外的变量
};
}
};
alert(object.getNameFunc()()); //this的作用域是在函数执行的时候确定,因为匿名函数是一个全局变量,因此this指向的是window对象
// 因此输出结果是:The Window
//-----代码二
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this; //--this指向object,保留了对象object在that里面,
return function(){
return that.name; //这里输出对象object的name属性的值
};
}
};
alert(object.getNameFunc()()); // My Object
function fun(n,o){
console.log(o);
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1); c.fun(2); c.fun(3);
//问:三行a,b,c的输出分别是什么?
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
//undefined 0 0 0
var b = fun(0).fun(1).fun(2).fun(3);
//undefined 0 1 2
var c = fun(0).fun(1); c.fun(2); c.fun(3);
//undefined 0 1 1
对于js中一些变量操作的取值问题:
对js中一些变量操作的理解
● 匿名自执行函数
a) 在js中声明变量的时候,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。
b) 有的函数只需要执行一次,其内部变量无需维护,比如UI的初始化,那么我们可以使用闭包:我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象
● 缓存—减少全局变量,减少出错的几率
再来看一个例子,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
● 封装
即将函数A内部的函数B作为A函数的返回值,这样在A函数外即可以访问到A函数内部的私有变量。
● 实现面向对象中的对象
虽然JavaScript中没有类这样的机制,但是通过使用闭包,我们可以模拟出这样的机制
带例子解释的链接:https://blog.csdn.net/sunlylorn/article/details/6534610
用闭包实现一个函数,每调用一次这个函数,变量的值就会自增
// 方法一
// var add = (function () {
// var counter = 0;
// return function () {return counter += 1;}
// })();
// console.log(add()); //1
// console.log(add()); //2
// console.log(add()); //3
// 方法二
function autoAddOne(){
var sum = 0;
return function(){
sum = sum +1;
return sum;
};
}
var tempFunc = autoAddOne();
console.log(tempFunc())
console.log(tempFunc())
方法二中之所以每次调用tempFunc()都会自增1,是因为在autoAddOne()中返回一个函数,该函数调用了autoAddOne()里面的变量,同时由于var声明的变量有提升的作用,tempFunc是一个全局变量,所以对返回函数的内存不释放,由于返回函数又调用了autoAddOne中的变量,所以这个函数所占用的内存也不释放
用什么方式可以实现外部函数访问到闭包里面的变量
将函数的返回值设定为一个对象,对象里面有一个属性是函数,该函数返回其里面定义的变量的值
function f1(){
var s1 = 1;
return {
f2:function(){
var temp = 1;
return temp;
}
};
}
var a1 = f1();
console.log(a1.f2());
使用闭包实现一个once函数
function once() {
var isFirst = true;
return function () {
if (isFirst) {
isFirst = !isFirst; //闭包对外部函数的变量的引用不释放,从而使引用的变量存放在内存中
console.log('test');
}
};
};
var b = once();
b(); // 'test'
b(); // nothing
//或者写成
var once = (function(){
var isFirst = true;
return function () {
if (isFirst) {
isFirst = !isFirst; //闭包对外部函数的变量的引用不释放,从而使引用的变量存放在内存中
console.log('test');
}
};
})()
在代理单例模式中,就使用闭包实现单例模式
let getSingle = function(fn){
let obj = null;
return function(){
obj ? obj : (obj = fn.apply(this,arguments));
}
};
如何强制释放闭包?
//强制释放闭包的方法,就是给保存闭包的变量,重新赋值为null
function f1(){
var n=999;
return function f2(){
alert(n);
}
}
var a = f1();
//当代码运行到这里,f1虽然已经执行完毕,但是变量n并未被回收,而是被变量a(也就是返回的函数f2)
//所引用着
a=null;
//代码到这里的时候,变量a又指向了null,a和函数f2的联系被切断。f2不再被引用,因此f2被回收
//f2被回收后,它对n的引用也就不存在了,因此同理,n不再被引用,n也被回收不再占用内存。