目录
闭包是什么
《犀牛书》
函数变量保存在函数作用域内部,这种特性成为闭包
《红宝书》
闭包是指有权访问另一个 函数作用域中的变量的函数 (函数没导出)
《你不知道的javascript》
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行
MDN
闭包可以让你在一个内层函数作用域中访问到其外层函数的作用域
当函数的执行导致函数被定义,并抛出(有些书里会定义未抛出,这个并不强制要求)
function foo(){
var n = 0
}
console.log(n) // Uncaught ReferenceError: n is not defined
function foo1(){
var m = 0; // m 是一个被foo1()创建的局部变量
function bar1(){// bar1 是内部函数 一个闭包
console.log(m) // 使用了父函数中声明的变量
}
bar1()
}
foo1()
在上面的代码中,闭包指的就是bar1()这个函数。
如何产生闭包
- 当一个嵌套的内部子函数引用了嵌套的外部父函数的变量(函数)时,就产生了闭包。注意:是在外部函数执行上下文中创建内部函数时闭包产生的,不是执行时,是创建时
产生闭包的条件
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数),执行函数定义就会产生闭包,不用调用内部函数,但是外部函数要调用。调用外部函数产生新的闭包,调用内部函数不会产生新的闭包
- 注意:闭包存在于嵌套的内部函数中
闭包的几种常见形式
1. 函数的嵌套之 函数的返回值是函数 (定义)
function foo1(fn){
var n = 0;
return function(){
}
}
foo1()
2. 函数嵌套之 函数的局部变量是函数 (定义)
function foo2(){
var n = function(){
}
return n
}
foo2()()
3. 函数嵌套之 全局变量定义为闭包
var outer;
function foo3(){
var a = 10
outer = function(){
console.log(a)
}
}
foo3() // outer 即闭包被定义
outer() // 10
4. 函数的参数的方式
var inner = function(fn){
console.log(fn())
}
function foo4(){
var b = "local";
var n = function(){ // 此为闭包,为一个返回值为 'local'字符串的函数
return b
}
inner(n); // 作为inner函数的参数
};
foo4() // 此函数被执行导致闭包 n 产生,并作为inner参数,返回 local
5. 循环赋值的方式
function foo5(){
var arr = [];
for(var i = 0; i < 10 ; i++){
arr[i] = (function(j){
return function(){
console.log(j)
}
})(i)
}
return arr
}
var bar = foo5();
for(var j = 0 ;j < 10; j++){
bar[j]();
}
6. 迭代器
var add =(function(){
var count = 0;
return function(){
return ++count;
}
})();
console.log(add());
console.log(add())
闭包的作用
变量作用域有两种:全局变量与局部变量;函数内部可以通过作用域链直接读取全局变量;在函数外部无法读取函数内部的变量
能够读取其他函数内部变量的函数,就是闭包
1、从外部读取函数内部的变量
function f1(){
var n=999;
function f2(){
console.log(n);
}
return f2;
}
var result=f1();
result(); // 999
以上代码中,将 f2 作为返回值,就可以在 f1 外部读取它的内部变量。本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁
2、将创建的变量的值始终保存在内存中
function f1() {
var n = 12;
function f2() {
console.log(++n);
};
return f2;
}
var result = f1();
result();//13
函数 f1 中的局部变量 n 一直保存在内存当中,并没有在 f1 被调用以后被自动清除
3、封装私有属性和私有方法
function f1(n) {
return function () {
return n++;
};
}
var a1 = f1(1);
a1() // 1
a1() // 2
a1() // 3
var a2 = f1(5);
a2() // 5
a2() // 6
a2() // 7
//这段代码中,a1 和 a2 是相互独立的,各自返回自己的私有变量。
闭包面试题
function fun(n,o){
console.log(o);
return {
// 返回值是一个对象,对象的key是fun,值为一个函数
fun:function(m){
return fun(m,n);
}
}
}
var a = fun(0) // undefined
a.fun(1) // fun(0).fun(1) 0
a.fun(2) // fun(0).fun(2) 0
a.fun(3) // fun(0).fun(3) 0
var b = fun(0) // undefined
.fun(1) // 0 // fun(0).fun(1)
.fun(2) // 1 // fun(0).fun(1).fun(2)
.fun(3) // 2 // fun(0).fun(1).fun(2).fun(3)
var c = fun(0).fun(1);
c.fun(2); // fun(0).fun(1).fun(2) // 1
c.fun(3); // fun(0).fun(1).fun(3) // 1
解析
对于第一组输出
fun(0)则 n 为 0,o 为 undefined,输出undefined
fun(0)返回一个对象{fun:function(m){return fun(m,n)}},对象的fun属性传入一个参数,即 function调用,且传入参数为 m, 返回 fun(m,n) m 为 1, n 在自己函数作用域中没有定义,向上一级作 用域中寻找,在全局作用域中可以找到 fun 函数的定义,所以 function(m){return fun(m,n)} 是闭 包,是fun函数的调用导致function 函数被定义,且可以访问到外部作用域中的变量 n ,所以其实是执行了fun(1,0),n被赋值为1,o被赋值为 0,输出o为 0 ; 后面不管m传入值变为1,2,3,执行的分别为fun(2,0),fun(3,0), 但是 o 值始终没有改变,所以持续输出 0
对于第二组输出
fun(0).fun(1) 输出同上为 执行fun(1,0),即fun(n=1,o=0) 的结果输出为 0
fun(0).fun(1).fun(2),由于fun(0).fun(1)实际为fun(1,0)执行,fun函数中的变量 n 被赋值为1,o被赋值为 0,输出 o 为 0,又执行fun(2),传入实参变为2,n为1 ,执行fun(2,1),即fun(n=2,o=1)n 变为 2,o变为1,输出为 1,然后fun又被传参 3,此次执行fun(m=3,n=2),即fun(n=3,o=2)n变为 3 ,o 变为 2, 输出为2
对于第三组输出
fun(0).fun(1) 输出同上为 执行fun(m=1,n=0) 的结果输出为 0
fun(0).fun(1).fun(2),由于fun(0).fun(1)实际为fun(m=2,n=1),即执行fun(n =2,o=1),fun函数中的变量 n 被赋值为2,o被赋值为 1,输出 o 为1;
fun(0).fun(1).fun(3),执行fun(m=3,n=1),即执行fun(n=3,o=1) 输出为1
闭包的生命周期
- 产生:在嵌套内部函数定义执行完了时就产生(不是调用,不是有函数提升嘛)。
- 死亡:在嵌套的内部函数成为垃圾对象时。
闭包的应用场景
1、使用闭包代替全局变量;全局变量有变量污染和变量安全等问题。
//全局变量,test1是全局变量
var test1=111
function outer(){
alert(test1);
}
outer(); //111
alert(test1); //111
2、函数外或在其他函数中访问某一函数内部的参数;
3、在函数执行之前为要执行的函数提供具体参数;
4、在函数执行之前为函数提供只有在函数执行或引用时才能知道的具体参数;
5、暂停执行;
6、包装相关功能。
参考:闭包的使用场景有哪些
闭包的缺点
缺点
- 函数执行完后,函数内部的局部变量没有释放,占有内存时间变长
- 容易造成内存泄漏
解决办法
- 能不用闭包就不用
- 及时释放(就让它等于null就ok,不要忘了!让内部函数成为垃圾对象–>回收闭包)
补充:内存溢出和内存泄漏
内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时,就出抛出内存溢出的错误
内存泄露
内存泄漏是指由于疏忽或错误造成程序未能释放已经不在使用的内存
内存泄漏并非指内存存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该内存之前就失去了对该内存的控制,
简单理解:无用的内存还在占用,得不到释放和归还,比较严重的时候,无用的内存还会增加,从而导致整个系统卡顿,甚至崩溃
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包