zoukankan      html  css  js  c++  java
  • 关于闭包的见解

      近日看到JavaScript高级程序设计第三版 7.2,终于解决了对闭包的疑惑。

    function func() {
        var i = 0;
        return function() {
            console.log(i++);
        }
    }
    var test = func();//第一次调用
    test();//第二次调用 0 
    test();//第三次调用 1
    test();//第四次调用 2
    func()();//直接一次调用 0
    func()();//直接一次调用 0
    

      

      上面这个函数可以说是标准的闭包,之前一直疑惑为什么要在定义闭包后调用两次函数。直到今天在chrome调试后才发现:

      第一次调用函数时,var test = func() ,只执行了 var i = 0 这句,碰到return function(){}时,将这个return 的函数地址存储赋予test。而return function(){}由于是返回值而不是IIFE,不调用的时,里面的内容都只会声明而不会执行。第二次调用函数时才会执行里面的代码。

      而func()() ,这样的直接一次调用会在每次调用的时候重新执行外部函数的代码,也就是 var i = 0,导致每次调用函数,变量都会被重置。

      所以 var test = func() ,这步相当于存储了执行后的外部函数,也就是执行一次 var i = 0 。而之后每次调用 test() ,都不会再次执行 var i = 0 ,同时 test() 每次执行时都会调用外部函数 func() 的活动对象 i ,保证了作用域链的链接,所以让 i++ 的结果得到存储。(也就是说,不会重复声明变量的情况下,可以只调用一次)

      此外闭包还有一些细节,例1:

    function func() {
        var closure = [];
        for (var i = 0; i < 10; i++) {
            closure[i] = function() {
                console.log(i);
            }
        }
        return closure;
    }
    var test = func();
    test[5]();            //10
    test[9]();            //10
    

      

      上面这个闭包会永远返回10。

      因为这个闭包中的 i 属于 func 函数的作用域里的变量,而一个作用域中的变量,同时只能拥有一个值。同时 for 循环中的 closure 只是声明,而没有调用。最后 return closure的时候, i 经过循环变为了10。因为作用域的变量只能拥有一个值,这时候就算是 test[0] 返回的也只是 func 作用域里的最后一个 i, 也就是 10。

      如果要存储这个for循环里面的所有i,可以使用下面这个方法:

    function func() {
        var arr = [];
        for (var i = 0; i < 10; i++) {
            arr[i] = function(num) {
                return function(){
                    return console.log(num);
                }
            }(i);
        }
        return arr;
    }
    var test = func();
    test[1]();    //1
    test[5]();    //5
    

      因为变量 i 传参给了 function(num){}(i) 这个函数,函数变量是按值传递的,每个i都会复制一次给num,由不同的地址存储起来。而num的作用域在匿名函数里面,所以不存在调用外层函数的活动对象,也就是保存着每一次的值。同时这是个匿名函数,会立即执行。执行时,而函数里面则又是一个闭包,函数内部的代码 return num 也就是对应的 i 给了 对应的arr[i]。

    如果将例1改成下面的样子

    function func() {
        var closure;
        for (var i = 0; i < 10; i++) {
          (function(j){        
            closure = function() {
                console.log(j);
            }
          })(i)
        }
        return closure;
    }
    var test = func();
    test();           //9
    test();           //9
    

      结果怎么调用都是9,也就是最后 i = 9; 而 i++ 没有执行。都是 9 是因为每次循环都重新给 closure 赋值,所以保留了最后的值。

      for里面是个IIFE,接受参数 j ,由外部作用域的变量 i 传递。 j 属于匿名函数的变量(匿名函数作用域包裹 j ),匿名函数模拟了块级作用域,每次循环重新生成一个作用域,里面的变量 j 随着作用域的不同,存储的地址也随之改变(当然最后的值都是9)。同时也不需要调用外层的活动对象,函数的参数是按值传递,每次传递给 j 都是值而不是地址,也不需要调用外层的活动对象。

      把例1的var i = 0;改成 let i = 0; (使用块级作用域)也能达到同样的效果,避免了引用外层函数的活动对象,循环时,每个成员 i 存储在一个独立的作用域。

      如果将例1的函数表达式:var closure = function(){} 替换成 return function(){} 。即下面这个形式:

    function func() {
        var i;
        for (i = 0; i < 10; i++) {
            return function() {
                console.log(i);
            }
        }
    }
    var test = func();
    test();
    

      这个例子无论多少次调用,结果都是 i = 0 。虽然是闭包,但是函数在执行 i++ 这一步的时候就已经return function(){} 了。所以调用多少次都永远是 i = 0 。所以这个函数等价于下面这个函数:

    function func() {
        var i = 0;
        return function() {
            console.log(i);
        }
    }
    var test = func();
    test();
    

      而这个下面这个函数虽然也是匿名函数闭包返回num。但由于不是数组,第一次调用时 closure 这个函数表达式for循环给 closure 赋值,而这个值是一个函数(实际上是一个指针,在第二次调用时才会执行里面的代码),最后一次赋值时 num = i = 9 ,之后由于 i++ === 10 所以跳出了循环 , 所以无论调用多少次,都不会再次执行for语句(num一直是9)。

    function func() {
        for (var i = 0; i < 10; i++) {
            var closure = function(num) {
                return function(){
                    return num;
                }
            }(i);
        }
        return closure;
    }
    var test = func();
    test();    //9
    

      下面这个例子用变量a存储起num的值,可以直观看出每次调用 test() 时,num 都是9。

    function func() {
        var a = 0;
        for (var i = 0; i < 10; i++) {
            var closure = function(num) {
                return function(){
                    a += num;
                    return console.log(a);
                }
            }(i);
        }
        return closure;
    }
    var test = func();
    test();     //9
    test();     //18
    test();     //27
    

      

      可以发现每次调用 test() 时,a的值都是9的倍数,证明每次调用 closure 里面的匿名函数时,返回的值都是9。

      而下面这个例子可以看出for循环只执行了一次(也就是 i 只进行了一轮赋值。调用函数 test() 的时候,闭包调用了变量对象,也就是 i 的最后一次赋值 9 )

    function func() {
        var i;
        for (i = 0; i < 10; i++) {
            var closure = function(num) {
                return function(){
                    return console.log(num++);
                }
            }(i);
        }
        return closure;
    }
    var test = func();
    test();    //9
    test();    //10
    test();    //11
    

      

      证明了for循环只进行了一次,所以在不使用运算符进行操作的时候, i 永远都是9。

      而下面这种方式则是错误的

    function func() {
        var i;
        var arr = [];
        for (i = 0; i < 10; i++) {
            arr = function(num) {
                return console.log(num);
            }(i);
        }
        return arr;
    }
    var test = func();//一次调用   等价于不用 var test 直接使用 func();
    

      上面这个函数由于只返回并调用了一次,看似是是闭包,实际上没闭包的效果,等价于下面的函数:

    function func() {
        var i;
        for (i = 0; i < 10; i++) {
            console.log(i);
        }
    }
    func();//一次调用
    

      

      实际上只是直接调用 func() 函数里面的代码而已,由于没有作用域链链接外部函数作用域,所以无论调用多少次 func() 都只会循环打印出 1-9。

      关于闭包中的this指向,首先一点,this永远指向函数被调时的对象,而不是定义的对象。

    var name = 'the window';
    var object = {
        name: "my object",
        method: function(){
            return function(){
                return this.name;
            };
        }
    }
    console.log(object.method()());    //the window
    

      

      解释1:object.method()() 实际上等于 (object.method())() 。也就是说object.method()虽然是对象方法(用hasOwnPrototypeProperty检测method可以知道)同时object.method()里的this指向object,但是 (object.method())() 则是一个普通函数。所以this指向的是全局对象window。

      解释2:由于闭包只能获取外部函数里面的活动对象,也就是外部函数里的 this, arguments 以及声明的变量。而调用object对象不会生成活动对象,所以闭包在查找外部活动对象时,跳过了object对象,导致this没有查找到object.name 而是window.name。

      像下面例子可以正常调用object对象里的this值。

    var name = 'the window';
    var object = {
        name: "my object",
        method: function(){
            var that = this;
            return function(){
                return that.name;
            };
        }
    }
    console.log(object.method()());    //my object
    

      那样先将method方法执行时的this地址赋值给变量 that ,由于闭包会查找外部变量的活动变量,所以that指向了object里的this地址,所以输出my object。

    调用都是 9 是因为每次循环都覆盖了原本的值。

  • 相关阅读:
    CSS 兼容性调试技巧
    CSS 常用的兼容性调试技巧
    全局CSS设置
    CSS 盒子模型
    CSS表格属性
    HTML引入CSS的方法
    CSS 定位
    CSS display overflow 属性 cursor光标类型
    CSS 继承和优先级
    沟通表达
  • 原文地址:https://www.cnblogs.com/NKnife/p/6179667.html
Copyright © 2011-2022 走看看