夜空中最亮的星 能否听清 那仰望的人 心底的孤独和叹息 夜空中最亮的星 能否记起 曾与我同行 消失在风里的身影 我祈祷拥有一颗透明的心灵 和会流泪的眼睛 给我再去相信的勇气 越过谎言去拥抱你 每当我找不到存在的意义 每当我迷失在黑夜里 夜空中最亮的星 请指引我靠近你 夜空中最亮的星 是否知道 曾与我同行的身影 如今在哪里 夜空中最亮的星 是否在意 是等太阳升起 还是意外先来临 我宁愿所有痛苦都留在心里 也不愿忘记你的眼睛 给我再去相信的勇气 越过谎言去拥抱你 每当我找不到存在的意义 每当我迷失在黑夜里 夜空中最亮的星 请照亮我前行 我祈祷拥有一颗透明的心灵 和会流泪的眼睛 给我再去相信的勇气 越过谎言去拥抱你 每当我找不到存在的意义 每当我迷失在黑夜里 夜空中最亮的星 请照亮我前行 夜空中最亮的星 能否听清 那仰望的人 心底的孤独和叹息 今天咱们来讨论一下 javascript 的这个闭包机制吧
曾经 javascript 的这个闭包的问题困惑了我很久很久
看网上那些说法看了半天都是懵的,看了书更加看不懂
过了好一段时间,在我巩固完一些基础知识之后
现在再回来看这个闭包机制,总算搞懂一些了

首先要弄明白闭包机制,必须对作用域这个东西非常熟悉
JavaScript 中,变量的局部作用域是函数级别的。
也就是说,每一个函数都对应一个作用域,每一个 function() { 后面都是一个新的子域
这没有什么好说的,只是值得一提的是,要能够区分函数级作用域和块级作用域的区别
比如在 C、C++、Java 这些语言中,它的局部作用就是块级别的,一对花括号就对应一个子域

public class Demo{
    public static String name = "微学苑";  // 类级变量
    public int i; // 对象实例级变量
    // 属性块,在类初始化属性时候运行
    {
        int j = 2;// 块级变量
    }
    public void test1() {
        int j = 3;  // 方法级变量
        if(j == 3) {
            int k = 5;  // 块级变量
        }
        // 这里不能访问块级变量,块级变量只能在块内部访问
        System.out.println("name=" + name + ", i=" + i + ", j=" + j);
    }
    public static void main(String[] args) {
        // 不创建对象,直接通过类名访问类级变量
        System.out.println(Demo.name);
       
        // 创建对象并访问它的方法
        Demo t = new Demo();
        t.test1();
    }
}


而在 javascript 中,它则是函数级别的,下面这个例子可以明确的区分出来

    function rainman(){
        // rainman函数体内存在三个局部变量 i j k
        var i = 0;
        if ( 1 ) {
            var j = 0;
            for(var k = 0; k < 3; k++) {
                alert( k );    //分别弹出 0 1 2
            }
            alert( k );        //弹出3
        }
        alert( j );            //弹出0
    }

这里拓展一下,其实为什么会这样子呢?
事实上, javascript 里面有一个机制,就是会把所有的变量声明给前置
也就是说,javascript 在执行脚本的时候,会把函数中所有的变量声明放到函数的最前面
这样子的话,自然而然的就能理解 javascript 不存在块级作用域的问题了

明白了 js 的作用域之后,接下来才能进入我们要讨论的正题,闭包
下面丢一个例子

function f(x) {
  var temp = x;
  return function(x) {
    temp += x;
    console.log(temp);
  }
}
var a = f(50);
//console.log((a);

a(5);
a(10);
a(20);

运行上面的例子,可以看到输出的结果是 55 , 65,85
也就是说,当 f(50) 这一句给运行完了以后,javascript 的回收机制没有去销毁函数 f(x) 中的 temp 变量
所以总结一下,对于上面这个 f(x) 函数,他有以下一些特点

  1. 它的返回值是一个函数
  2. 它的返回值函数中调用了其上一级作用域的变量
  3. 它的 temp 变量因为被其子函数给引用,所以不会被销毁
  4. 函数外部无法直接访问 temp ,却能在外部通过调用其子函数来修改 temp 的值

根据以上的特点,可以不难发现闭包可以实现一些封装的特性,所以现在的闭包可以用于面向对象的一些封装
对于使用闭包,还有以下几点需要注意一下

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


参考资料:

《作用域与闭包:this,var,(function () {})》
学习Javascript闭包(Closure)
闭包 - JavaScript | MDN
详解js闭包 - SegmentFault
JavaScript闭包 - 腾讯Web前端 IMWeb 团队社区
深入理解JavaScript的变量作用域
Java变量的作用域_微学苑