闭包

什么是闭包

  • 就是在一个外部函数内部创建另一个函数

  • 全局调用内部函数的时候可以访问到外部函数的局部变量,并使用!

闭包的使用场景:

  • setTimeout

  • 回调

  • 封装变量

  • 事件处理程序

  • 模块模式

优缺点:

  • 逻辑连续,当闭包作为另一个函数调用参数时,避免脱离当前逻辑而单独编写额外逻辑

  • 方便调用上下文的局部变量。

  • 加强封装性,是第2点的延伸,可以达到对变量的保护作用。

  • 延长作用域链:局部变量存在函数中,函数使用完后,局部变量会自动释放,闭包后,局部变量还存在被调用的可能,没有被及时释放,作用域链延长。

闭包会使得函数中的变量都被保存在内存中,内存消耗很大,在IE中可能导致内存泄露。 (动态存储分配函数内存空间,在使用完毕后未释放,导致一直占用该内存单元,直到程序结束)

- 垃圾清除

  1. 标记清除(常用):垃圾回收机制在运行时会给存储在内存中的所有变量加上标记(可以使用任何标记方式)。然后去掉环境中的变量和被环境变中的变量引用的变量的标记。而在此之后被加上标记的变量被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后垃圾收集器完成内存清除工作,销毁带标记的值并回收他们所占用的内存空间

  2. 引用计数:reference counting 含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数为1.如果同一个值又被赋给另一个变量,引用次数加1.相反的,变量包含这个值得引用变量又取得另一个值,则这个值的引用次数减1.当引用次数为0时,将其占用内存回收。(循环引用的变量和函数无法回收)

  3. 解决:将完成的函数或变量重置为null。

闭包的模块应用分析

在 JavaScript 中,闭包最常见的应用是模块模式。模块允许你定义外部不可见的私有实现 细节(变量、函数),同时也可以提供允许从外部访问的公开 API。

function User(){    
    var username, password; 
 
    function doLogin(user,pw) {       
        username = user;         
        password = pw; 
 
        // 执行剩下的登录工作    
    }    
    var publicAPI = {      
        login: doLogin  
    }; 
 
    return publicAPI;
} 
 
// 创建一个User模块实例
var fred = User();  // 。User() 只是一个函数,而不是需要实例化的类,所以只是正常调用就可 以了。使用 new 是不合适的,实际上也是浪费资源。
 
fred.login( "fred", "12Battery34!" );
// 函数User() 用 作 外 层 作 用 域, 持 有 变 量username 和 password,以及内层的函数 doLogin();这些都是这个 User 模块私有的内部细节,无法从外部访问。

执行 User() 创建了 User 模块的一个实例,这创建了一个新的作用域,因而创建了所有内 层变量 / 函数的一个新副本。我们将这个实例赋给 fred。如果再次运行 User(),那么会得 到一个不同于 fred 的全新实例。

内层的函数 doLogin() 在 username 和 password 上有一个闭包,这意味着即使在 User() 函 数运行完毕之后,函数 doLogin() 也保持着对它们的访问权。

publicAPI 是带有一个属性 / 方法 login 的对象,login 是对内层函数 doLogin() 的一个引 用。当我们从 User() 返回 publicAPI 时,它就变成了我们命名为 fred 的那个实例。

此时,外层的函数 User() 已经运行完毕。我们通常认为像 username 和 password 这样的内 层变量也就随之消失了。但上述示例并不会这样,因为 login() 函数的内部有一个可以使 得它们依然保持活跃的闭包。

这就是我们可以调用 fred.login(..) 的原因,这等同于调用内层 doLogin(..),并且 fred. login(..) 仍然可以访问内层变量 username 和 password。

你可能感兴趣的:(闭包)