目录

闭包

  • 函数嵌套,闭包 :一个函数就是一个闭包,包里面可以访问包外面任意函数,变量,而包外面对包里面是不可访问的

    function a() {
      var aa = "i am aa";
      function b() {
        var bb = "i am bb";
        function c() {
          var cc = "i am cc";
          bb();
        }
        //a();  // 无限循环了
      }
      console.log(aa);
      b();
    }
    a();
    b(); //b is not defined

作用域链

  • 作用域链 :从全局对象 衍生下来的一条链子,访问一个变量时,会从链子的末端开始查找,到顶端 window 还没找到,就报 undefined 错误了 。每个函数定义时,都会存下一个作用域链 。

  • 作用域链对于 闭包的理解: 只能从 链子末端 向 顶端查找 。所以函数内能访问函数外,而函数外不能访问函数内。

  • 作用域链对于 with() 的理解: with 是强制 将一个链子的 某个节点 拿过来用!

上下文

  • 函数是一直处于上下文中的,这里的上下文指的就是 js 对象

  • js 浏览器中,window 对象就是最大的,从定义函数的地方开始往外推,碰见的第一个对象,就是它的上下文。

  • 通过在 函数中 alert(this); 来判断处于哪个对象中!函数中能操作的属性和能调用的方法都是该上下文的!

函数作为返回值

  • 不返回求和的结果,而是返回求和的函数!

    function lazy_sum(arr) {
      var sum = function() {
        return arr.reduce(function(x, y) {
          return x + y;
        });
      };
      return sum;
    }
  • 当我们调用 lazy_sum()时,返回的并不是求和结果,而是求和函数:

    var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()
    //调用函数f时,才真正计算求和的结果:
    f(); // 15
  • 函数 lazy_sum 中又定义了函数 sum,内部函数 sum 可以引用外部函数 lazy_sum 的参数和局部变量,当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中

  • 当我们调用 lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

    var f1 = lazy_sum([1, 2, 3, 4, 5]);
    var f2 = lazy_sum([1, 2, 3, 4, 5]);
    f1 === f2; // false f1()和f2()的调用结果互不影响。
  • 返回的函数在其定义内部引用了局部变量 arr,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用

  • 返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

    // 每次循环,都创建了一个新的函数,然后,把创建的 3 个函数都添加到一个 Array 中返回
    function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
    arr.push(function () {
    return i \* i;
    });
    }
    return arr;
    }
    
        var results = count();
        var f1 = results[0];
        var f2 = results[1];
        var f3 = results[2];
    
        f1(); // 16
        f2(); // 16
        f3(); // 16

原因就在于返回的函数引用了变量 i,但它并非立刻执行。等到 3 个函数都返回时,它们所引用的变量 i 已经变成了 4,因此最终结果为 16。

  • 如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

    function count() {
      var arr = [];
      for (var i = 1; i <= 3; i++) {
        arr.push(
          (function(n) {
            return function() {
              return n * n;
            };
          })(i)
        );
      }
      return arr;
    }
    
    var results = count();
    var f1 = results[0];
    var f2 = results[1];
    var f3 = results[2];
    
    f1(); // 1
    f2(); // 4
    f3(); // 9
  • Java 和 C++,要在对象内部封装一个私有变量,可以用 private 修饰一个成员变量。

  • 在没有 class 机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用 JavaScript 创建一个计数器:

    'use strict';
    
        function create_counter(initial) {
            var x = initial || 0;
            return {
                inc: function () {
                    x += 1;
                    return x;
                }
            }
        }
        var c1 = create_counter();
        c1.inc(); // 1
        c1.inc(); // 2
        c1.inc(); // 3
    
        var c2 = create_counter(10);
        c2.inc(); // 11
        c2.inc(); // 12
        c2.inc(); // 13

在返回的对象中,实现了一个闭包,该闭包携带了局部变量 x,并且,从外部代码根本无法访问到变量 x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。

  • 闭包还可以把多参数的函数变成单参数的函数.

    function make_pow(n) {
      return function(x) {
        return Math.pow(x, n);
      };
    }
    
    // 创建两个新函数:
    var pow2 = make_pow(2);
    var pow3 = make_pow(3);
    
    pow2(5); // 25
    pow3(7); // 343