虽然JavaScript是一门完整的面向对象的编程语言,但这门语言同时也拥有许多函数式语言的特性.
函数是语言的鼻祖是LISP,JavaScript在设计之初参考了LISP两大方言之一的Scheme,引入了Lambda表达式,闭包,高阶函数等特性.使用这些特性,我们经常可以用一些灵活而巧妙的方式来编写JavaScript代码.
闭包
变量的作用域
在JavaScript中,函数可以用来创造函数作用域.此时的函数向一层半透明的玻璃,在函数里面可以看到外面的变量,而在函数外面则无法看到函数里面的变量.这是因为在函数搜索一个变量的时候,如果该函数没有声明这个变量,那么此次搜索的过程会随着代码执行环境创建的作用域往外层逐层搜索,一直搜索到全局对象为止.变量的搜索是内到外而非从外到内的.
变量的生存周期
除了变量的作用域之外,另外一个跟闭包有关的概念是变量的生存周期.
对于全局变量来说,全局变量的生存周期当然是永久的,除非我们主动销毁这个全局变量.
而对于在函数内的var关键字声明的局部变量来说,当退出函数是,这些局部变量即失去了它们的价值,它们都会随着函数调用的结束而被销毁.
但当退出函数后,局部变量a并没有消失,而是似乎一直在某个地方存活着.这是因为当执行var f = func();时,f返回了一个匿名函数的引用,它可以访问到func()被调用时产生的环境,而局部变量a一直处在这个环境里.既然局部变量所在的环境还能被外界访问,这个局部变量就有了不被销毁的理由.在这里产生了一个闭包结构,局部变量的生命看起来被延续了.(函数的作用域在函数创建之初就已经被确定,不会因为被调用而改变!)
闭包和面向对象设计
过程与数据的结合是形容面向对象中的"对象"时经常使用的表达.对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据.通常用面向对象思想能实现的功能,用闭包也能实现.反之亦然.
闭包与内存管理
局部变量本来应该在函数退出的时候解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去.从和这个意义上看,闭包的确会使一些数据无法被及时销毁.使用闭包的一部分原因是我们选择主动把一些变量封闭在闭包中,因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,这里并不能说成是内存泄露,如果在将来需要回收这些变量,我们可以手动把这些变量设为Null.
跟闭包和内存泄露有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,这时候就有可能造成内存泄露,但这本身并非闭包的问题,也并非JavScript的问题,在IE浏览器中,由于 BOM和DOM中的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾会师集机制采用的是引用计数策略.在基于引用计策略的垃圾回收机制中,如果两者对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄漏的本质上也不是闭包造成的.
同样,如果要解决循环引用带来的内存泄露问题,我们需要把循环引用中的变量设为null即可.将变量设置为null意味着切断变量与它此前引用的值之间的连接.当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存.
网友评论