在学习前端的过程中,不可避免的要学习到js闭包这个知识点,很多朋友感到对闭包很难理解,也不清楚它有什么用。本文就详细介绍一下闭包,并通过几个小例子来说明下闭包的用处。
一、闭包的概念
闭包的英文单词是Closure,我先给闭包可以这样下个简单的定义,这个定义不是官方的,是我自己理解的。
定义:如果在函数A的内部,声明了另外一个函数B,并且函数B可以访问A中定义的变量或是数据,此时函数A和函数B就形成了闭包。
闭包其实讲述了函数与函数的关系。
二、闭包的基本形式
我们直接来个例子加以说明:
例1:闭包基本形式
这里定义了一个函数f1,在f1的内部又定义了一个局部变量num和函数f2,并且f2调用了局部变量num,这个代码结构已经形成了闭包。
不过,这样的代码看看也罢,貌似是没有任何作用的。我们再改改。
例2.闭包基本形式2
查看运行结果,我们看到输出了10 。 我们下一个简单的结论:闭包可以让一个局部的变量,在它的作用域之外访问到。您可能不同意我这个结论。不过没关系,请继续往下看。
三、闭包的模式
闭包其实有2种模式:
1.函数形式的闭包
2.对象形式的闭包
咱们刚才在上边的例子都是函数形式的闭包。我们举一个对象模式的闭包,作为了解。
例3.对象模式的闭包
这里函数f1和它内部的对象obj形成了闭包,原因是:1.obj是在f1内部声明的,2.obj的age属性访问了f1内部声明的局部变量num。
好了,最常见的闭包,还是函数模式的,所以这个了解就好,我们下面的例子都是以函数模式来讲解的。
四、闭包的作用
闭包有什么用呢?它的作用在于两点:1.延长局部变量的作用域链。2.缓存数据。其实第2点就是通过第1点来达到的效果。我们举个例子。
例4.闭包作用演示1
我们前面的例子仅仅是在f1的内部声明了一个f2,而这里,我们不仅在f1内部声明了一个函数,并且把这个内部的函数作为外部函数f1的返回值return了回来。
这下就有趣了。当我们执行语句:var fun = f1();的时候,变量fun里存储的是什么?当然是f1函数的返回值,只不过这时候的返回值恰好又是一个函数对象。其实在这里,就fun相当于是一个函数表达式了。这条语句的调用结果,等价于下面这种写法
例5.简单函数表达式语法
这就是一个简单的函数表达式,既然如此,fun当然可以通过fun()的形式来调用这个函数。不过这都不是重点。
重点是,我们在全局作用域下,通过fun这个引用,访问到了f1里定义的局部变量num。
你如果觉得不是,你再想想,我是不是能这样:
例6.闭包作用演示2
修改过后,是不是已经说明了问题,什么问题?我们确实在全局作用域下,拿到了一个函数内部声明的局部变量的值。或者换句话说,我们在函数外部访问到了内部声明的局部变量。
看到这里,你可能还是觉得有点迷糊,我是访问到了,但是这和在内部函数中直接console.log()输出这个局部变量num的值有什么区别呢?你说的访问,也仅仅是读而已,并不能代表能操作它,比如改变它的值,所以你并不认为例6是真正的在外部访问了内部的数据。您是不是也有如此的疑惑呢?
下面再看一组例子,为您解惑。
例7.非闭包的数据访问操作
请问,此例中代码执行结果是什么?答案是输出3次11 。 这个原因很简单,3次完全独立的函数调用而已,所以每次调用的时候都会开辟一个全新的内存空间来存储f1中声明的局部变量num,并且赋初值为10,那么++过后必然都是11 。所以这个输出结果毫无悬念。
再看这个例子的变形,也就是例8
例8.闭包版的数据访问操作
先说输出结果吧,3次调用,输出的是
11,12,13 这个结果奇怪吗?不奇怪吗? 这就是闭包的作用。
在代码的第27行,我们声明了一个变量fun,给他复制为f1()函数执行的返回值,也就是内部声明的那个函数对象。接下来3次调用都是使用的同一个对象,而这个fun指向的函数对象内部访问了定义它的外部函数f1声明的一个局部变量num。所以,3次调用fun()时,操作的num++,都是针对内存里的同一个变量进行的++,所以我们看到的结果就是11,12,13 。下面通过一个图来说明下例8
说明下图上表示的意思。
1.黑色的大矩形框,表示代码执行时的js环境
2.红色矩形框代表js引擎线程在执行代码是的内存空间。当然这个代码在执行时是有先后的,内存也会有先后的变化,不过我为了简单起见,把所有的过程都画一张图里了。
3.小的黑色矩形框表示在预解析时,已经加载到内存中的f1函数的代码
那么运行结果是这样的,首先执行var fun = f1(); 这时候会把小黑方框中的代码加载到主线程去执行,执行的结果就是得到那2个蓝色的矩形框。
小一点的是那个num变量,大一点的蓝色矩形框代表那个返回的函数对象。从这张图我们能清晰的看到,整个代码在执行时,只有1个函数对象fun,也只有一个变量num。由于num被fun对象所引用,所以,虽然超出了它的作用域,它也无法释放掉。这也证明了,我们说的,闭包延长了变量的作用域链。
现在我们可以得出结论了,闭包的作用就是:
1.延长变量的作用域链。
2.缓存数据