zoukankan      html  css  js  c++  java
  • 深刻理解JavaScript---闭包

     

    闭包是指那些能够访问独立(自由)变量的函数 (变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以“记忆”它被创建时候的环境。——这句话其实有点难以理解。我觉得应该用一些例子来理解闭包的含义。

    • 闭包#1

    先来看一个函数:

    1
    2
    3
    4
    5
    6
    function f(){
    var b = "b";
    return function(){
    return b;
    }
    }

    这个函数含有一个局部变量b,它在全局空间里面是不可见的。而f()的返回值是另一个匿名函数,该函数有自己的私有的空间,可以访问f()的空间和全局空间,所以b对它来说是可见的。

    1
    2
    var n = f();
    n();//此时控制台输出"b"

    因为f()是一个全局函数,它可以在全局空间被调用,当然也可以将其返回值赋值给另一个全局变量n,从而生成一个可以访问f()私有空间的新的全局函数。

    • 闭包#2

    再来看看下面这个跟上面差不多的函数,在这里f()不再返回函数了,而是直接在函数体内创建一个新的全局函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var n;
    function f(){
    var b = "b";
    n = function(){
    return b;
    }
    }
    f();
    n();//调用f(),然后再调用n(),控制台输出"b"

    由于n()没有使用var语句,因此它是全局的,同时它也可以访问f()的作用域,所以哪怕n()最后升级为全局函数,但它依然可以保留对f()作用域的访问权。

    • 闭包#3

    函数通常会让自身的参数视为局部变量,所以我们创建返回函数时,也可以令其返回父级函数的参数,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function f(arg){
    var n = function(){
    return arg;
    };
    arg++;
    return n;
    }
    var m = f(123);
    m();//调用函数,输出124

    有一点很重要:n被赋值时函数并没有被调用,调用发生是在n被求值,也就是执行return n;语句时。由此可以看出,函数所绑定的是作用域本身,而不是该作用域中的变量或变量当前所返回的值。

    • 循环中的闭包#4

    这种闭包所导致的bug往往很难被发现,因为它们表面上看起来很正常,来看一下下面的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function f(){
    var a = [];
    var i;
    for (i = 0; i < 3; i++){
    a[i] = function(){
    return i;
    }
    }
    return a;
    }
    //下面来运行一下函数,并将结果赋值给数组a
    var a = f();
    a[0]();//输出3
    a[1]();//输出3
    a[2]();//输出3

    为啥不是0、1、2呢?为啥会这样呢?原来在这里创建的三个闭包,它们都指向了一个共同的局部变量i,但是,闭包不会记录它们的值,它们所拥有的的只是一个i的连接(即引用),因此只能返回i当前值,因为i结束循环时值为3,所以这三个函数都指向一个共同值3

    如何纠正?显然,需要a[i]指向三个不同的变量,下面是解决方案之一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function f(){
    var a = [];
    var i;
    for (i = 0; i < 3; i++){
    a[i] = (function(x){
    return function(){
    return x;
    }
    })(i);
    }
    return a;
    }
    //下面来运行一下函数,并将结果赋值给数组a
    var a = f();
    a[0]();//输出0
    a[1]();//输出1
    a[2]();//输出2

    这里使用了自调函数,不再直接返回i的值,而是将i传递给自调函数,i赋值给了局部变量x,这样一来,每次迭代x就会拥有各自不同的值了。

    解决方案二:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function f(){
    function aa(x){
    return function(){
    return x;
    }
    }
    var a = [];
    var i;
    for (i = 0; i < 3; i++){
    a[i] = aa(i);
    }
    return a;
    }

    方案二不使用自调函数,而是定义了一个内部函数实现相同的功能,每次迭代操作中,将i的值“本地化”。

    • Getter与Setter、 迭代器 闭包的应用示例#5
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var getValue, setValue;
    (function(){
    var temp = 0;
    getValue = function(){
    return temp;
    };
    setValue = function(v){
    temp = v;
    }
    })();
    getValue();//输出0
    setValue(123)
    getValue();//输出123

    这个例子是通过一个匿名自调函数来实现的,定义的全局函数setValue()和getValue(),确保局部变量temp的不可直接访问性。

    下面是一个接受数组输入的初始化函数,其中定义了一个私有指针,该指针会指向数组中的下一个元素。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function setup(x){
    var i = 0;
    return function() {
    return x[i++];
    };
    }
    //调用setup(),创建我们所需的next()函数
    var next = setup(['a', 'b', 'c']);
    //重复调用next(),就可以不停的获取下一个元素
    next();//输出"a"
    next();//输出"b"
    next();//输出"c"

    到这里闭包就暂时告一段落了,以后有新的理解再写。

  • 相关阅读:
    C#多线程编程实战1.5检测线程状态
    C#多线程编程实战1.4终止线程
    C#多线程编程实战1.3等待线程
    C#多线程编程实战1.2暂停线程(休眠)
    C#多线程编程实战1.1创建线程
    C#中base的作用
    C#继承
    C#中return的两个作用
    Windows下完全卸载node.js并安装node.js的多版本管理工具nvm-windows
    执行gulp build报错
  • 原文地址:https://www.cnblogs.com/libin-1/p/6013497.html
Copyright © 2011-2022 走看看