在JavaScript高级程序设计中,闭包的定义是,有权访问另一个函数作用域中的变量的函数,实现闭包的方式就是在函数中嵌套另一个函数。
闭包三大特点:
-
函数嵌套函数
归根结底,闭包就是一个函数,可以这么理解,任何函数都是一个闭包,全局函数是全局作用域的内函数,是window的闭包
-
内层函数能够访问外层的变量
闭包的作用域链是从当前执行环境,向包含函数延申,一直延伸到全局作用域
-
变量和参数不会被垃圾回收机制回收
变量和参数存在于闭包的作用域链中,所以一直被闭包函数引用,除非销毁闭包函数才会被从内存中销毁
涉及执行环境,作用域,内存的部分,会单独写一个博客梳理
以一个例子说明闭包特性:
function outer(){ var count = 10; return function(){//嵌套内层闭包函数 console.log(count*=2);//闭包内访问包含函数变量 } } var outFn1 = outer(); //outFn1是指向闭包函数的一个指针 outFn1();//20 outFn1();//40 outFn1();//80 //count变量没有被销毁 var outFn2 = outer();//创建另一个指向闭包函数的指针outFn2 outFn2();//20 不同的引用之间不会产生影响
闭包踩坑
-
只能取得包含函数中任何变量的最后一个值
举例说明:
function count(){ var arr = []; for(var i = 0;i<10;i++){ arr[i] = function(){ return i; } } return arr; }
这段代码返回了一个包含10个函数的数组,按照预想,每个函数应该返回对应的索引值,但是事实上每个函数都返回了10,因为每个函数的
作用域链
中都保存着fun() 函数的活动对象
,所以它们引用的都是同一个变量 i,每个函数返回得都是10另外创建一个匿名函数达到预期:
function count(){ var arr = []; for(var i = 0;i<10;i++){ arr[i] = function(num){ return function(){ return num; } }(i) } return arr; }
这段代码中没有直接把闭包函数赋值给数组,而是通过自执行函数,在闭包里又创建了一个闭包,把i通过外层闭包传递进去,内层闭包输出,这样数组每个函数都维护自己的一个num
用let声明变量i也能达到预期效果 -
闭包中的this
函数中的this是基于函数的执行环境绑定,但是在闭包中,可能不会跟想象中的一样,因为闭包具有全局性,所以闭包中的this指向window(通过call和apply作用也能改变this的指向)``` var name = 'window'; var obj = { name:'obj', getName:function(){ return function(){ console.log(this.name); } } } obj.getName()(); ``` >打印结果没有按照预期,this指向并没有指向调用函数的对象obj
-
内存泄漏
如果闭包作用域链中保存html元素,则该元素无法销毁。闭包会引用包含函数的整个活动对象,需要将保存着html的变量设置为null解除引用。function assignHandler(){ var element = document.getElementById("someElement"); var id = element.id; element.onclick = function(){ alert(id); }; element = null; }
闭包应用
-
代替全局变量,避免命名冲突
为了避免使用全局变量,可以通过使用立即执行函数定义临时变量,子函数即是闭包函数
-
延长局部变量的生命周期,使之一直保存在内存中
-
增加块级作用域
(function(){ var result='hello'; function fun1(){ alert(result); } fun1(); //hello 形成闭包 )(); alert(result); //在立即执行函数外部,这里就访问不到result变量
因为要记录上一次函数运行的时间戳,而且不能因为函数执行完而销毁,要么创建全局变量,但是过多的全局变量容易造成污染,因此用闭包,闭包使preTime变量不被内存回收