JavaScript闭包专题

闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来

  • 没看懂?我也是。不过我找到一种更加通俗但是可能不太全面的说法:闭包就是能够读取其他函数内部变量的函数,也就是说,我们可以简单的理解为:“++闭包是定义在一个函数内部的函数++”
  • 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。
  • 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

众所周知,JavaScript中内部函数可以引用其父元素的参数及局部变量。由此我们衍生出了map()函数用来接收函数作为自身参数。然而还有另一种更加好用的方法—-闭包

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外

对于map()函数来说,只能返回结果。但是若暂时不需要结果,我们就可以利用高阶函数的特性:返回函数(闭包亦是高阶函数的一种)

上一篇博客我们提到,js中函数可以作为返回值。闭包正是利用了这一点。由于返回的函数在其内部引用了局部变量arr,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。所以闭包看起来简单,实现起来可不简单。

闲话不说我们先看代码

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];

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:

f1(); // 16
f2(); // 16
f3(); // 16

注意:返回的函数并没有立刻执行,而是直到调用了f()才执行

咳咳,是不是对结果非常疑惑?学习中我也有此类困惑。事实上其特性是这样的:返回的函数引用了变量i,但其并非立即执行,而是++等到三个函数都返回时++,他们引用的变量i已经变成了4,因此最终结果变成了16。所以长点记性,千万!千万不要放在返回函数中引用任何循环变量

当然,循环作为代码重要的组成部分想省略是不太现实的,这时就需要我们换种方法:再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变

因此,上边的代码我们可以这样实现避免出现三个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

此处分享干货:“创建一个匿名函数并立刻执行”语法

(function (x) {
    return x * x;
})(3); // 9

不过,该方法还需要注意:必须将整个函数定义括起来,否则会报SyntaxError错误

通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:

(function (x) {
    return x * x;
})(3);
  • 方才给大家展示了js中闭包的一个功能:返回一个函数并延迟执行
  • 在面向对象的语言中有private用来修饰成员变量,而在只有函数没有class机机制的js中,我们同样可以利用闭包封装私有变量
  • eg:用js创建一个计数器
'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。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
- 同时,闭包还有一个作用:将多参数函数变成单参数的函数。我们来举几个例子
- 将函数x^y(Math.pow(x, y))计算公式改为x^2 或 x^3,函数名为pow2,pow3

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
  • 说了这么多,我们来详细讨论一下闭包的用途吧
    • 读取函数内部的变量
    • 让函数内部的变量的值始终保持在内存中
function f1(){  
  var n=999;  
  nAdd=function(){n+=1}  
  function f2(){  
    alert(n);  
  }  
  return f2;  
}  
var result=f1();  
result(); // 999  
nAdd();  
result(); // 1000  

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除

疑惑?咱们得详细说一下:f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是”nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

下面我们进行一下系统讲解
调用result时也相当于调用了f1
但是外部父函数无法访问子函数的变量
所以nAdd+1的作用也相当于没有发挥出来,最后return f2的时候还是返回了999
而在外部调用了一次nAdd之后n的值更新,而因为闭包可以让变量的初始值始终保持在内存中,所以n直接更新为1000
然后进到函数内输出

  • 最后我们再总结一下使用闭包的注意项吧
    • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
    • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
  • 最后我们放两段代码来加深大家的理解。(据说理解这俩闭包就理解的差不多了)
    • eg1
var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      return function(){
        return this.name;
      };
    }
  };
  alert(object.getNameFunc()());
    • eg2
var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()());

最后,声明本篇博客后半部分是复制粘贴了网上某大神的博客,具体出处已不得而知,但还是表示由衷的感谢。关于闭包在面试中仍是一大考点,鉴于个人水平有限,已经从网上摘录了几篇不错的文章于我的收藏之中,方便查看

9种办法解决JS闭包经典面试题之for循环取i:http://blog.csdn.net/u013243347/article/details/52134643

JS闭包导致循环给按钮添加事件时总是执行最后一个:http://blog.csdn.net/xiaozji/article/details/43530563

for循环中的闭包问题及解决方案:http://blog.csdn.net/qq_34986769/article/details/52144307

js 解决js for 循环中的闭包问题:http://blog.csdn.net/z_572712675/article/details/71274315

for循环里面嵌套if的问题的问题:http://ask.csdn.net/questions/202388

JS之经典for循环闭包问题解决方法:http://blog.csdn.net/yuli_zoe/article/details/43305855

猜你喜欢

转载自blog.csdn.net/qq_38722097/article/details/80552233