共计 2581 个字符,预计需要花费 7 分钟才能阅读完成。
一、闭包的概念
闭包官方给出的解释:闭包是函数和声明该函数的词法环境的组合。
下面我们举一个经典的且最简单的闭包 demo
function closure(){ var a=4; function clo(){console.log(a) } clo();} closure();
从上面的例子中,我们可以看出闭包需要的三基本个条件是:1. 一个外层函数。2. 一个内部函数。3. 局部变量。
二、闭包的用途
要理解闭包的用途首先要理解 javascript 的特殊的变量作用域。
变量的作用域只有两种:全部变量和局部变量。而 javascript 语言中,函数内部可以直接读取全局变量,但是函数外部无法读取函数内部的局部变量。
而我们需要用到的闭包的场景就是:我们需要一个外部不可以直接访问一个函数内部变量的的环境。因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。比如:插件封装,面向对象编程等。
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。
1.DOM 中使用闭包实例
<a href="#" id="size-12">12</a> <a href="#" id="size-14">14</a> <a href="#" id="size-16">16</a> function makeSizer(size) {return function() {document.body.style.fontSize = size + 'px';}; } var size12 = makeSizer(12); var size14 = makeSizer(14); var size16 = makeSizer(16); document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16;
2. 用闭包模拟私有变量
function closure2(){ var pCounter=0; function changeBy(val){pCounter += val;} return{add:function(){changeBy(1) }, reduce:function(){changeBy(-1) }, value:function(){return pCounter} } } var counter1= closure2(); var counter2 = closure2(); console.log(Counter1.value()); Counter1.add(); Counter1.add(); console.log(Counter1.value()); /* logs 2 */ Counter1.reduce(); console.log(Counter1.value()); /* logs 1 */ console.log(Counter2.value()); /* logs 0 */
每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。
三、闭包的缺点
1. 缺点一:变量发生变化
function arr(){var res=new Array(); for (var i = 0; i< 10; i++){res[i]=function(){console.log(i); } } return res; } var arr1=arr(); arr1[0]();//10 arr1[1]();//10
因为闭包里面的函数调用发生在 for 循环结束之后, 此时变量 i 的值是 10,且 res 组成的闭包集合共用一个词法环境里面的变量 i,因此每个闭包所输出的值都是 10。
解决此类情况的方式:
a. 使用立即执行函数
function arr(){var res=new Array(); for (var i = 0; i< 10; i++){res[i]=(function (num) {return function(){console.log(num) } })(i) } return res; } var arr1=arr(); arr1[0]();//0 arr1[1]();//1
b. 使用 es6 中的 let
function arr(){var res=new Array(); for (let i = 0; i< 10; i++){res[i]=function () {console.log(i) } } return res; } var arr1=arr(); arr1[0]();//0 arr1[1]();//1
这两种方式的共性就是把变量 i, 变成了每个函数的局部变量,因此在执行闭包的时候,局部变量 i 不会发生变化。
2. 缺点二:this 指向问题
var obj = { name:"this", getName:function() {return function () {console.log(this.name) } } } var arr= obj.getName(); arr()//undefined
this 指向当前调用函数的上下文,arr 函数创建在全局环境中,所以在调用 arr 时,在非严格模式的浏览器环境中 this 指向 window,在严格模式下 this 是 undefined,而这两种情况下都没有一个全局的 name, 所以函数的执行结果为 undefined。
3. 缺点三:内存泄漏
function showId() {var el = document.getElementById("div1") el.onclick = function(){console.log(el.id) // 这样会导致闭包引用外层的 el,当执行完 showId 后,el 无法释放 } } // 改成下面 function showId() {var el = document.getElementById("app") var id = el.id el.onclick = function(){console.log(id) // 这样会导致闭包引用外层的 el,当执行完 showId 后,el 无法释放 } el = null // 主动释放 el }
当闭包中引用了全局变量时,记得在引用完之后进行释放,避免造成内存泄漏。