• JavaScript 学习24.函数闭包(closure) 上海


    前言

    闭包是面试最喜欢问的一个问题了,面试官最喜欢问:
    1.什么是闭包?2.闭包的作用是什么?

    什么是闭包?

    闭包(closure)是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰,即形成一个不销毁的栈环境。

    闭包的特性:

    • 函数嵌套函数
    • 内部函数可以访问外部函数的变量
    • 参数和变量不会被回收。

    先看一个典型的闭包, 实现计数器的功能

    function myCounter() {
       var counter = 0;
    		   
        function add() {
    	counter += 1;
    	console.log(counter);
        }
        return add;
    }
     
    var myAdd = myCounter();
    myAdd();
    myAdd();
    myAdd();// 计数器现在为 3
    

    在弄清楚闭包之前,我们需要弄明白一个概念:私有的作用域

    变量作用域

    var 声明的变量有2个作用域:全局作用域 和局部作用域。
    let 和 const 声明的变量有3个作用域:全局作用域 和局部作用域, 还有个块作用域。块作用域这里就不讲了

    全局作用域

    在函数外部var 声明的变量就是全局作用域

    var count = 0
    
    // 每次调用fun() count 加1
    function fun() {
        count += 1;
    }
    
    fun();
    fun();
    fun();
    console.log(count);  // 3
    

    函数局部作用域

    在函数内部声明的变量是函数局部作用域

    var count = 0
    
    // 每次调用fun() count 加1
    function fun() {
        var count = 0;
        count += 1;
    }
    
    fun();
    fun();
    fun();
    console.log(count);  // 0
    

    虽然函数内部也声明了一个count变量,但是跟函数外部的count其实是2个不同的变量,所以每次调用函数,仅仅只是内部变量count加1了,但是外部的count还是0。

    计数器问题

    如果我们想设置一个计数器,当我们打开一个页面,只有5次点击机会,点完就不能再点击了。

    <body>
    <button type="button" id="btn"></button>
    <script>
    var counter = 5;
    
    // 每次调用fun() count -1
    function fun() {
        counter -= 1;
        if (counter <= 0){
            document.getElementById('btn').disabled = true;
            return
        }
    
    }
    
    // 设置初始值
    document.getElementById('btn').innerHTML = '还剩' + counter + '次';
    // 每点一次减1
    document.getElementById('btn').onclick = function () {
        fun();
        document.getElementById('btn').innerHTML = '还剩' + counter + '次';
    }
    </script>
    </body>
    

    实现效果

    每点一次会减1

    最后为0时不可被点击

    看起来是没什么问题,但是这个会有一个安全隐患,由于counter变量是全局的,所以在其它任何地方都可以改变全局变量的值。
    如果用户在控制台,改变了全局变量counter的值,比如我在console执行var counter=100

    当我点一下按钮,就会变成99次

    闭包的作用

    为了解决这种全局变量导致的问题,所以我们需要在函数内部设置一个私有变量

    <body>
    <button type="button" id="btn"></button>
    <script>
    // 每次调用fun() count -1
    function fun() {
        var counter = 5;
        document.getElementById('btn').innerHTML = '还剩' + counter + '次';
        function inner(){
            counter -= 1;
            if (counter <= 0){
                document.getElementById('btn').disabled = true;
            }
            return counter
        }
        return inner
    }
    mycounter = fun();
    // 每点一次减1
    document.getElementById('btn').onclick = function () {
        var x = mycounter();
        document.getElementById('btn').innerHTML = '还剩' + x + '次';
    }
    </script>
    </body>
    

    或者把counter作为一个变量传给函数,此时也不会影响

    <body>
    <button type="button" id="btn"></button>
    <script>
    // 每次调用fun() count -1
    function fun(counter) {
        document.getElementById('btn').innerHTML = '还剩' + counter + '次';
        function inner(){
            counter -= 1;
            if (counter <= 0){
                document.getElementById('btn').disabled = true;
            }
            return counter
        }
        return inner
    }
    
    var counter = 5;
    mycounter = fun(counter);
    
    // 每点一次减1
    document.getElementById('btn').onclick = function () {
        var x = mycounter();
        document.getElementById('btn').innerHTML = '还剩' + x + '次';
    }
    </script>
    </body>
    

    因为 fun()函数只执行一次,所以会锁定局部变量counter。

    计数器

    如果是从0开始,每运行一次函数加1,那么可以优化成以下代码

    <body>
    <button type="button" onclick="myFunction()">计数!</button>
    <p id="demo"></p>
    <script>
    // 自调用函数
    var add = (function () {
        var counter = 0;
        return function () {return counter += 1;}
    })();
    
    function myFunction(){
        document.getElementById("demo").innerHTML = add();
    }
    </script>
    </body>
    

    每点一次按钮会加1

    闭包的作用

    综上所述,闭包的作用就是在外部a函数执行后,闭包使得Javascript的垃圾回收机制不会收回a所占用的资源,因为a内部函数的变量要给到内部函数b继续使用。
    那么闭包的好处有以下几点:

    • 保护函数内的变量安全
    • 在内存中维持一个变量(用的太多就变成了缺点,占内存) ;
    • 逻辑连续,当闭包作为另一个函数调用的参数时,避免你脱离当前逻辑而单独编写额外逻辑。
    • 方便调用上下文的局部变量。
    • 加强封装性,可以达到对变量的保护作用。
  • 相关阅读:
    Spring.Net初认识——竹子整理
    SOA:面向服务编程——竹子整理
    unity安装记录
    wcf第三方客户端与wcf服务之间调用入门
    Winform VS2015打包
    OWINS是什么(转载)
    [LR]遇到的坑及常用技巧
    性能测试简单调优
    es6解构赋值
    es6 笔记
  • 原文地址:https://www.cnblogs.com/yoyoketang/p/16307578.html
Copyright © 2020-2023  润新知