闭包是一种保护局部变量不受污染的机制,无非三个关键要素:受保护的局部变量,外层函数,内层函数。它将局部变量与外界隔离开来,只对外开放内部函数的引用,这样一来,只要外部变量对内部函数的引用一直存在,由于作用链的原因,局部变量便不会随着外部函数撤出内存而消失,它会一直存在于内存中。
闭包产生的过程如下:
我们知道,js在运行时会将所有的变量和函数的声明提前,也就是说,不管变量在什么地方定义的,在程序执行的最开始它们就已经在内存中存在了,各自占据了自己的变量空间。每个函数运行时,都会进入栈,并创建一个作用域对象(AO),里面存放了在该作用域上的所有变量。函数的作用域在函数调用之前就已经声明好了的,理解这点很重要。大多数时候,我们会被一些复杂的函数调用困扰,其实只要查看函数的定义内容,便能知道其作用域了,某些变量能不能使用能不能访问,也是由作用域决定的。
js程序在运行时,首先将main函数入栈(知道java,c++的人都知道,main函数是程序运行的入口,js中也是有main函数的),然后创建main函数的作用域对象(AO),main变量的score,,,指向AO。这个AO就是全局对象(window),里面封装了所有的全局变量。如图:
定义完毕,继续运行程序,当执行到fun()时,调用函数,fun便入栈,同时创建fun函数的作用域对象AO,里面同样封装了fun函数中的变量。在fun的AO中,有一个parent变量,里面保存了其上一层函数的地址(即引用)。所以在fun内部可以通过parent访问到全局变量。同时,内部函数的引用也赋值给了f,当当用f()时,便执行了f变量引用的代码段。如图:
f()调用了fun的内部函数,内部函数入栈,fun调用完毕,出栈,从上图中我们可以看出,main函数一直存在(只有函数执行完毕时,main才会出栈)。而f一直引用内部函数对象,内部函数对像中又有对对funAO引用的变量,所以AO不会随着fun的出栈而出栈。
以前一直不理解函数对象与作用域的关系(如途中的fun对象与fun的AO),现在似乎有点灵感了。函数对象封装的是一段具有一定逻辑的程序,调用函数便是执行这段程序的过程,而函数的执行依赖于数据,数据都保存在变量中,js的世界里,所有的变量都封装在每个函数特有的作用域里。所以函数与其作用域间是相互独立而又互相依存的关系。函数对象是固定不变的(即保存在其里面的逻辑代码段不会变),作用域对象却随着每次对象的调用而新建。我们常说的新调用一次函数,其实也就是新创建一个作用域的过程,使用新数据的过程。
作用域对象会保留对其上一层作用域的引用,函数对象也会保存对齐上一层函数对象的引用。
最后调用内部函数,内部函数入栈,创建新的数据链,内部函数可以访问其上层间的所有函数的数据(变量)。最终图如下:
其中,作用域链便是三个作用域对象。