JavaScript 闭包(Closure)

Closure 闭包

闭包是函数和声明该函数的词法环境的组合.类似于c#中的委托,或者说是一个栈帧,在函数开始是被分配到堆,当函数返回后仍然不会被释放.

Profile

  1. 在function中使用另一个function, 就会使用闭包.但是构造器函数New Function()不会使用闭包
  2. 闭包可以视作一个函数的入口以及与此函数的相关的局部变量(函数退出时的局部变量的副本)的组合
  3. 每次调用有闭包的函数, 都会保留一组新的局部变量(如果函数包含一个内部函数, 并且内部函数的引用被返回或以某种方式保留)
  4. 因为隐秘的闭包, 两个函数看起来可能有相同的源代码, 但实际的作用完全不同
  5. 闭包在处理速度和内存消耗方面对脚本有负面影响
  6. 闭包可用来构建JavaScript私有成员

用途

  1. 访问变量(在变量的作用域内,多在外部函数中声明),被赋值给一个变量,
  2. 作为函数的实参被传递,
  3. 作为函数结果被返回.
function sayHello(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');//Hello Joe 

函数作为引用被返回

function sayHello2(name) {
  var text = 'Hello2 ' + name; // Local variable
  return function() { console.log(text); }
}
sayHello2('Joe')();//Hello2 Joe 等价于 var say2 = sayHello2('Joe');say2();

闭包是什么:与指针的对比

  1. C指针不同的是,JavaScript中函数引用变量不仅指向函数,还包括隐藏的指针指向闭包,一块儿分配在堆上的函数所处上下文环境的内存
  2. C等其他语言中在函数返回后,因为栈帧被销毁,其局部变量就不再可访问.相对的,JavaScript中在函数中声明一个函数,即使调用的函数被返回,外部函数中的局部变量仍旧可用.
    sayHello2('Joe')()中的text是局部变量,匿名方法可以访问text,就是因为sayHello2()仍旧保存在一个闭包中.JavaScript中函数引用有一个对它的闭包的秘密引用,类似于委托,方法指针加上一个对对象的引用.
    它们按引用保存.外部函数退出时,栈帧仍保存在内存中
function sayHello3() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = sayHello3();
sayNumber(); // logs 43

闭包按引用保存

  1. 同一个闭包可以同时被多个函数所访问, setupSomeGlobals()中的局部变量可以同时被3个方法调用
  2. 但是, setupSomeGlobals一旦被重新调用, 新的闭包就会被创建, 堆上一块新的内存被分配.
    var gLogNumber, gIncreaseNumber, gSetNumber;
    function setupSomeGlobals() {
      // Local variable that ends up within closure
      var num = 42;
      // Store some references to functions as global variables
      gLogNumber = function() { console.log(num); }
      gIncreaseNumber = function() { num++; }
      gSetNumber = function(x) { num = x; }
    } 
    setupSomeGlobals();gIncreaseNumber();
    gLogNumber(); //43 
    gSetNumber(6);
    gLogNumber();//6 
    var oldLog = gLogNumber; setupSomeGlobals();
    gLogNumber(); //42 
    oldLog();//6

进阶: 共享一个闭包的函数数组

buildList返回的是根据list的个数生成的方法数组, 共享一个闭包, 也包括其局部变量i
fnlist[j]()调用匿名函数(function(){console.log(item + '_' + i +'_'+ list[i])})时, 都指向同一闭包
此时var声明的索引变量i作用域是整个for循环体, 因为for循环已经遍历完成, 匿名函数对应的局部变量都变成了3
而let声明的变量k的作用域是匿名函数, 不同的遍历阶段, 匿名函数中的k值是不一致的

    //var
    function buildList(list){
        var result = [];
        for(var i=0; i<list.length; i++){
            var item = 'item' + i;
            result.push(function(){console.log(item + '_' + i +'_'+ list[i])});
        }
        return result;
    }
    function testList() {
        var fnlist = buildList([1,2,3]);
        // Using j only to help prevent confusion -- could use i.
        for (var j = 0; j < fnlist.length; j++) {
            fnlist[j]();
        }
    }
    testList() // 打印3次 item2_3_undefined
    //let
    function buildList(list){
        var result = [];
        for(let k=0; k<list.length; k++){
            let item = 'item' + k;
            result.push(function(){console.log(item + '_' + k +'_'+ list[k])});
        }
        return result;
    }
    testList(); //item0_0_1 \n item1_1_2 \n item2_2_3

猜你喜欢

转载自blog.csdn.net/sgs595595/article/details/80085162