JS学习笔记之闭包

闭包的概念、作用

当在函数内部定义了其他函数时,就创建了闭包(closures)。闭包有权访问函数内部的所有变量,原理如下:

  • 当某个函数被调用时,会创建一个执行环境及相应的作用域链,然后,使用arguments和其他命名参数的值来初始化函数的活动对象。
  • 在后台执行环境中,闭包的作用域链包含着他自己的作用域、包含函数的作用域和全局作用域。
  • 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁
  • 但是,当函数返回一个闭包时,这个函数的作用域将会一直在内存中保存直到闭包不存在为止,即当函数返回闭包后,其执行环境的作用域链会被销毁,但他的活动对象仍会留在内存中,直到匿名函数被销毁后,函数的活动对象才会被销毁。
  • 闭包只能取得包含函数中任何变量的最后一个值。

    闭包的使用场景

1、使用闭包可以在JavaScript中模仿块级作用域(JavaScript本身没有块级作用域的概念)要点如下:

  • 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用;
  • 结果就是函数内部的所有变量都会立即被销毁–除非将某些变量赋值给包含作用域(即外部作用域)中的变量;
  • 调用函数的方式,是在函数名称后面加一对圆括号,因为JavaScript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号,然而函数表达式的后面可以跟圆括号;
  • 将函数声明包含在一对圆括号中,他实际上表示一个函数表达式~用作块级作用域,私有作用域的匿名函数。后面可以加上一对圆括号~表示立即执行这个函数。

2、闭包还可以用于在对象中创建私有变量,利用私有和特权,隐藏那些不应该被直接更改的属性。要点如下:

  • 任何在函数中定义的变量,都可以认为是私有变量,因为不能再函数的外部访问这些变量,私有变量包括函数的参数、局部变量和在函数内部定义的其他函数;
  • 即使JavaScript中没有正式的私有对象属性的概念,但是可以在函数内部创建一个闭包,那么闭包通过自己的作用域也可以访问这些变量,从而创建访问私有变量的公有方法;
  • 有权访问私有变量的公有方法叫做特权方法;
  • 在构造函数中定义特权方法的缺点:必须使用构造函数模式来实现,构造函数模式的缺点就是针对每个实例都会创建同一组新方法,无法体现方法的复用。
    1
    2
    3
    4
    5
    for (var i=1; i<10; i++) {
    setTimeout( function timer(){
    console.log( i );
    },1000 );
    }//输出10个10;

闭包会携带包含它的函数的作用域,这种作用域链的配置引出一个值得注意的副作用:闭包只能取得包含函数中任何变量的最后一个值。因为闭包保存的是整个变量对象,而不是某个特殊的变量。

1
2
3
4
5
6
7
for (var i=1; i<10; i++) {
(function(j){
setTimeout( function timer(){
console.log( j );
}, 1000 );
})( i );
}//输出1~9

按值传递参数,立即执行,而不是直接引用外部作用于中的i。

闭包使用可能造成的不良后果

好处:能够实现封装和缓存等;

坏处:就是消耗内存、不正当使用会造成内存溢出的问题。

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

参考: