最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 面试官:谈谈对JS闭包的理解及常见应用场景(闭包的作用)

    正文概述 掘金(random__)   2021-03-19   472

    对JS闭包的理解及常见应用场景(闭包的作用)

    使用闭包主要为了设计私有的方法和变量。

    • 优点是可以避免变量的污染
    • 缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。

    在js中,函数即闭包,函数才会产生作用域的概念。

    1.变量作用域

    变量作用域两种:全局变量局部变量。js中函数内部可以读取全局变量,函数外部不能读取函数内部的局部变量。

    2.如何从外部读取函数内部的变量?

    function f1(){
            var n = 123;
            function f2(){    //f2是一个闭包
                alert(n)
            }    
            return f2;
        }
    
    • js链式作用域:子对象会一级一级向上寻找所有父对象的变量,反之不行。
    • f2可以读取f1中的变量,只要把f2作为返回值,就可以在f1外读取f1内部变量

    3.闭包概念

    • 能够读取其他函数内部变量的函数。
    • 或简单理解为定义在一个函数内部的函数,内部函数持有外部函数内变量的引用。

    4.闭包用途

    • 读取函数内部的变量
    • 让这些变量的值始终保持在内存中。不会在f1调用后被自动清除。
    • 方便调用上下文的局部变量。利于代码封装。

    原因:f1是f2的父函数,f2被赋给了一个全局变量,f2始终存在内存中,f2的存在依赖f1,因此f1也始终存在内存中,不会在调用结束后,被垃圾回收机制回收。

    5.闭包的理解

    来几个例子:

    /**
     * [init description]
     * @return {[type]} [description]
     */
    function init() {
        var name = "Chrome";    //创建局部变量name和局部函数alertName
    
        function alertName() { //alertName()是函数内部方法,是一个闭包
            alert(name); //使用了外部函数声明的变量,内部函数可以访问外部函数的变量
        }
        alertName();
    }
    init();
    

    一个变量在源码中声明的位置作为它的作用域,同时嵌套的函数可以访问到其外层作用域中声明的变量

    /**
     * [outFun description]
     * @return {[type]} [description]
     */
    function outFun(){
        var name = "Chrome";
        function alertName(){
            alert(name);
        }
        return alertName;   //alertName被外部函数作为返回值返回了,返回的是一个闭包
    }
    
    var myFun = outFun();
    myFun();
    

    闭包有函数+它的词法环境;
    词法环境指函数创建时可访问的所有变量。
    myFun引用了一个闭包,闭包由alertName()和闭包创建时存在的“Chrome”字符串组成。
    alertName()持有了name的引用,
    myFunc持有了alertName()的的访问,
    因此myFunc调用时,name还是处于可以访问的状态。

    /**
     * [add description]
     * @param {[type]} x [description]
     */
    function add(x){
        return function(y){
            return x + y;
        };
    }
    
    var addFun1 = add(4);
    var addFun2 = add(9);
    
    console.log(addFun1(2)); //6
    console.log(addFun2(2));  //11
    

    add接受一个参数x,返回一个函数,它的参数是y,返回x+y
    add是一个函数工厂,传入一个参数,就可以创建一个参数和其他参数求值的函数。
    addFun1和addFun2都是闭包。他们使用相同的函数定义,但词法环境不同,addFun1中x是4,后者是5

    6.闭包应用场景

    setTimeout传参

    //原生的setTimeout传递的第一个函数不能带参数
    setTimeout(function(param){
        alert(param)
    },1000)
    
    
    //通过闭包可以实现传参效果
    function func(param){
        return function(){
            alert(param)
        }
    }
    var f1 = func(1);
    setTimeout(f1,1000);
    

    回调

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title></title>
        <link rel="stylesheet" href="">
    </head>
    <style>
        body{
            font-size: 12px;
        }
        h1{
            font-size: 1.5rem;
        }
        h2{
            font-size: 1.2rem;
        }
    </style>
    <body>
    
        <p>哈哈哈哈哈哈</p>
        <h1>hhhhhhhhh</h1>
        <h2>qqqqqqqqq</h2>
    
        <a href="#" id="size-12">12</a>
        <a href="#" id="size-14">14</a>
        <a href="#" id="size-16">16</a>
    
    <script>
        function changeSize(size){
            return function(){
                document.body.style.fontSize = size + 'px';
            };
        }
    
        var size12 = changeSize(12);
        var size14 = changeSize(14);
        var size16 = changeSize(16);
    
        document.getElementById('size-12').onclick = size12;
        document.getElementById('size-14').onclick = size14;
        document.getElementById('size-16').onclick = size16;
        //我们定义行为,然后把它关联到某个用户事件上(点击或者按键)。我们的代码通常会作为一个回调(事件触发时调用的函数)绑定到事件上
    </script>
    </body>
    </html>
    

    封装变量

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>闭包模拟私有方法</title>
        <link rel="stylesheet" href="">
    </head>
    <body>
    <script>
        //用闭包定义能访问私有函数和私有变量的公有函数。
        var counter = (function(){
            var privateCounter = 0; //私有变量
            function change(val){
                privateCounter += val;
            }
            return {
                increment:function(){   //三个闭包共享一个词法环境
                    change(1);
                },
                decrement:function(){
                    change(-1);
                },
                value:function(){
                    return privateCounter;
                }
            };
        })();
    
        console.log(counter.value());//0
        counter.increment();
        counter.increment();//2
        //共享的环境创建在一个匿名函数体内,立即执行。
        //环境中有一个局部变量一个局部函数,通过匿名函数返回的对象的三个公共函数访问。
    
    </script>
    </body>
    </html>
    

    为节点循环绑定click事件

    面试官:谈谈对JS闭包的理解及常见应用场景(闭包的作用)

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <link rel="stylesheet" href="">
    </head>
    <body>
    
    <p id="info">123</p>
    <p>E-mail: <input type="text" id="email" name="email"></p>
    <p>Name: <input type="text" id="name" name="name"></p>
    <p>Age: <input type="text" id="age" name="age"></p>
    
    <script>
    function showContent(content){
        document.getElementById('info').innerHTML = content;
    };
    
    function setContent(){
        var infoArr = [
            {'id':'email','content':'your email address'},
            {'id':'name','content':'your name'},
            {'id':'age','content':'your age'}
        ];
        for (var i = 0; i < infoArr.length; i++) {
            var item = infoArr[i];
            document.getElementById(item.id).onfocus = function(){
                showContent(item.content)
            }
        }
    }
    setContent()
    
    </script>
    </body>
    </html>
    
    

    上述代码原本想实现,点击不同的框显示不同的信息,结果现在都只会显示最后一项,"your age"

    分析

    • 循环中创建了三个闭包,他们使用了相同的词法环境item,item.content是变化的变量
    • 当onfocus执行时,item.content才确定,此时循环已经结束,三个闭包共享的item已经指向数组最后一项。

    解决

    /**
     * 解决方法1     通过函数工厂,则函数为每一个回调都创建一个新的词法环境
     */
    function showContent(content){
        document.getElementById('info').innerHTML = content;
    };
    
    function callBack(content){
        return function(){
            showContent(content)
        }
    };
    
    function setContent(){
        var infoArr = [
            {'id':'email','content':'your email address'},
            {'id':'name','content':'your name'},
            {'id':'age','content':'your age'}
        ];
        for (var i = 0; i < infoArr.length; i++) {
            var item = infoArr[i];
            document.getElementById(item.id).onfocus = callBack(item.content)
        }
    }
    setContent()
    
    /**
     * 解决方法2        绑定事件放在立即执行函数中
     */
    function showContent(content){
        document.getElementById('info').innerHTML = content;
    };
    
    function setContent(){
        var infoArr = [
            {'id':'email','content':'your email address'},
            {'id':'name','content':'your name'},
            {'id':'age','content':'your age'}
        ];
        for (var i = 0; i < infoArr.length; i++) {
            (function(){
                var item = infoArr[i];
                document.getElementById(item.id).onfocus = function(){
                    showContent(item.content)
                }
            })()//放立即执行函数,立即绑定,用每次的值绑定到事件上,而不是循环结束的值
        }
    }
    setContent()
    
    /**
     * 解决方案3        用ES6声明,避免声明提前,作用域只在当前块内
     */
    function showContent(content){
        document.getElementById('info').innerHTML = content;
    };
    
    function setContent(){
        var infoArr = [
            {'id':'email','content':'your email address'},
            {'id':'name','content':'your name'},
            {'id':'age','content':'your age'}
        ];
        for (var i = 0; i < infoArr.length; i++) {
            let item = infoArr[i];      //限制作用域只在当前块内
            document.getElementById(item.id).onfocus = function(){
                showContent(item.content)
            }
        }
    }
    setContent()
    

    7.优缺点及解决办法

    优点

    • 避免全局变量的污染
    • 能够读取函数内部的变量
    • 可以在内存中维护一个变量

    缺点

    • 1.闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
    • 2.闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

    闭包,不会在调用结束后被垃圾回收机制回收,这里拓展一下垃圾回收机制

    8.拓展·垃圾回收机制

    当内存不再需要使用时,需要将其释放,这里最艰难的任务是找到“哪些被分配的内存确实已经不再需要了”。这就需要垃圾回收机制来判定了。

    1.引用计数垃圾收集

    这是最初级的垃圾收集算法。
    此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。
    如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

    例子:

    var o = {
      a: {
        b:2
      }
    };
    // 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
    // 很显然,没有一个可以被垃圾收集
    
    
    var o2 = o; // o2变量是第二个对“这个对象”的引用
    
    o = 1;      // 现在,“这个对象”只有一个o2变量的引用了,“这个对象”的原始引用o已经没有
    
    var oa = o2.a; // 引用“这个对象”的a属性
                   // 现在,“这个对象”有两个引用了,一个是o2,一个是oa
    
    o2 = "yo"; // 虽然最初的对象现在已经是零引用了,可以被垃圾回收了
               // 但是它的属性a的对象还在被oa引用,所以还不能回收
    
    oa = null; // a属性的那个对象现在也是零引用了
               // 它可以被垃圾回收了
    

    限制:循环引用

    该算法有个限制:无法处理循环引用的事例。在下面的例子中,两个对象被创建,并互相引用,形成了一个循环。它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。

    function f(){
      var o = {};
      var o2 = {};
      o.a = o2; // o 引用 o2
      o2.a = o; // o2 引用 o
    
      return "azerty";
    }
    
    f();
    

    循环引用实际例子

    IE 6, 7 使用引用计数方式对 DOM 对象进行垃圾回收。该方式常常造成对象被循环引用时内存发生泄漏:

    var div;
    window.onload = function(){
      div = document.getElementById("myDivElement");
      div.circularReference = div;
      div.lotsOfData = new Array(10000).join("*");
    };
    

    在上面的例子里,myDivElement 这个 DOM 元素里的 circularReference 属性引用了 myDivElement,造成了循环引用。如果该属性没有显示移除或者设为 null,引用计数式垃圾收集器将总是且至少有一个引用,并将一直保持在内存里的 DOM 元素,即使其从DOM 树中删去了。如果这个 DOM 元素拥有大量的数据 (如上的 lotsOfData 属性),而这个数据占用的内存将永远不会被释放。

    2.标记-清除算法

    这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。

    这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

    这个算法比前一个要好,因为“有零引用的对象”总是不可获得的,但是相反却不一定,参考“循环引用”。

    从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。

    循环引用不再是问题了 在上面引用计数的示例中,函数调用返回之后,两个对象从全局对象出发无法获取。因此,他们将会被垃圾回收器回收。第二个示例同样,一旦 div 和其事件处理无法从根获取到,他们将会被垃圾回收器回收。

    限制: 那些无法从根对象查询到的对象都将被清除 尽管这是一个限制,但实践中我们很少会碰到类似的情况,所以开发者不太会去关心垃圾回收机制。


    起源地下载网 » 面试官:谈谈对JS闭包的理解及常见应用场景(闭包的作用)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元