• JavaScript 闭包系列一


    一. 闭包的概念 

    闭包是有权访问另一个函数作用域中的变量的函数

    如下代码:根据变量作用域,函数outer中所有的局部变量对函数inner都是可见的。但是反过来不行,inner内部的局部变量对outer是不可见的。这就是JavaScript语言特有的链式作用域结构(chain scope),子对象会一级一级向上寻找所有父对象的变量。所以父对象的所有变量对子对象都是可见的,反之则不成立。

    function outer() {
        var i = 100;
        function inner() {
            console.log(i);    //100
        }
        inner();
    }
    outer();

    既然函数inner能读取函数outer的局部变量,那么只要将inner作为返回值,就可以直接在outer外部读取它内部的变量。

    function outer() {
        var i = 100;
        function inner() {
            console.log(i);
        }
        return inner;
    }
    var res = outer();
    res();   //100

    这个函数有两个特点:

    1)函数inner嵌套在函数outer内部

    2)函数outer返回函数inner

    执行完var rs = outer()后,实际rs指向函数inner。这段代码其实就是一个闭包。也就是说当函数outer内的函数inner函数被函数outer外的一个变量引用的时候,就创建了一个闭包。

    二. 闭包的作用

    function outer() {
        var i = 100;
        function inner() {
            console.log(i++);
        }
        return inner;
    }
    var rs = outer();
    rs();   //100
    rs();   //101
    rs();   //102

    上面的代码中,rs是闭包inner函数。rs共运行了3次,第一次100,第二次101,第三次102,这说明函数outer中的局部变量i一直保存在内存中,并没有在调用后清除。

    闭包的作用:在outer执行完毕并返回后,闭包是JavaScript的垃圾回收机制(garbage collection)不会回收outer所占的内存,因为outer函数内部的inner函数执行要依赖outer中的变量。(另一种解释:outer是inner的父函数,inner被赋给了一个全局变量,导致inner会一直在内存中,而inner的存在依赖于outer,因此outer也一直存在于内存中,不会在调用结束后被垃圾回收机制(garbage collection)回收。) 

    由上述可得知:

    1)闭包有权访问函数内部的所有变量;

    2)当函数返回一个闭包时,这个函数的作用域会一直在内存中保存直到闭包不存在为止。 

    三. 闭包与变量 

    作用链机制,闭包允许内层函数引用父函数中的变量,但该变量是最终值

    var f = function() {
        var rs = [];
        for(var i=0; i<10; i++) {
            rs[i] = function() {
                return i;
            }
        }
        return rs;
    };
    var fn = f();
    for(var i=0; i<fn.length; i++) {
        console.log("函数fn(" + i + ")返回值是:" + fn[i]());
    }

    上述代码执行后,fn为一个数组,表面上看,每个函数执行后应该返回自己的索引值,但实际上,每个函数都返回10。fn数组中每个函数的作用域链上都保存着函数f的活动对象,它们引用的是同一变量i。当函数f返回后,i的值为10。

    强制创建另一个闭包让函数的行为符合预期,如下代码所示。

    var f = function() {
        var rs = [];
        for(var i=0; i<10; i++) {
            rs[i] = (function(num) {
                return function() {
                    return num;
                }
            })(i);
        }
        return rs;
    };
    var fn = f();
    for(var i=0; i<fn.length; i++) {
        console.log("函数fn(" + i + ")返回值是:" + fn[i]());
    }

    这个版本中,没有直接将闭包赋值给数组,而是定义了一个匿名函数,并将立即执行匿名函数的结果赋值给数组。这里的匿名函数有一个参数num,在调用每个函数时,传入变量i,由于参数是按值传递的,因此将变量i复制给参数num。而在这个匿名函数内部又创建并返回了一个访问num的闭包,因此rs数组中每个函数包含自己的num变量,因此就可以返回不同的数值。

    四. 闭包中this对象

    var name = "Jack";
    var o = {
        name: "Ting",
        getName: function() {
            return function() {
                return this.name;
            }
        }
    };
    console.log(o.getName()());   //Jack

    var name = "Jack";
    var o = {
        name: "Ting",
        getName: function() {
            var self = this;
            return function() {
                return self.name;
            }
        }
    };
    console.log(o.getName()());   //Ting

    五. 内存泄露

    如下代码:创建了作为el元素事件处理程序的闭包,而这个闭包又创建了循环引用,只要匿名函数存在,el的引用数至少为1,因此它所占用的内存就永远不会被回收。

    function assignHandler() {
        var el = document.getElementById('demo');
        el.onclick = function() {
            console.log(el.id);
        }
    }
    assignHandler();

    如下代码:把变量el设置为null,能够解除对DOM对象的引用,确保正常回收其占用内存。

    function assignHandler() {
        var el = document.getElementById('demo');
        var id = el.id;
        el.onclick = function() {
            console.log(id);
        }
        el = null;
    }
    assignHandler();

    六. 模仿块级作用域

    任何一对花括号中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,称之为块级作用域。

    (function() {
        //块级作用域
    })();


    七. 闭包的应用

    1)读取函数内部的变量

    如上述例子中, 函数inner能够访问函数outer中的变量,将函数inner作为返回值,就能直接在outer外部读取它的变量。

    2)在内存中维持变量

    如上述例子中,由于闭包,函数outer一直存在于内存中,因此每次执行rs(),都会给i加1。

    时间:2014-10-23

    地点:合肥

    引用:http://wlog.cn/javascript/javascript-closures.html

  • 相关阅读:
    Docker多主机互联
    数据结构
    广度优先算法走出迷宫
    golang反射
    waitGroup的使用
    golang中的mutex锁
    goroutine和channel
    如何优雅的关闭Golang Channel?
    使用context关闭协程以及协程中的协程
    golang对不同系统的编译
  • 原文地址:https://www.cnblogs.com/sun-mile-rain/p/4043089.html
Copyright © 2020-2023  润新知