• javascript中的闭包


    前言

      本篇是基于对 《你不知道的JavaScript(上卷)》中的第五章的总结理解。

    不完全正确的概念

      简单通俗的说,函数内嵌函数就是闭包。但不完全正确,最重要的是内部函数执行时仍然持有外层作用域内的引用。

    闭包解释

    function outer() {
            var a = 2
            function inner(){
                console.log(a)
            }
    
            return inner
    }
    
    var fun = outer()
    fun()

      上述代码就是一个闭包。

    1. 内部函数 inner 的作用域包括自身作用域外还向外涵盖,所以 inner 内可以使用 outer 内的变量 a;
    2. outer 函数将 inner 函数作为返回值。

      一般来说,outer 函数执行完后,内部的变量 a 本来要被垃圾回收,但是其内部的函数 inner 有在使用 变量 a,为了 fun(也就是 inner ) 能够何时何地的正确执行,所以变量 a 不会被回收(保留了outer内的作用域)。这就是闭包。再看:

    var fun
        function outer() {
            var a = 2
            function inner(){
                console.log(a)
            }
    
            fun = inner
    }
    
    outer()
    fun()

      上述代码也是闭包。将内部函数 inner 赋值给fun。为了 inner 能够正确执行,变量 a 没有被回收。

      所有,无论使用何种方法将内部函数传递到外层作用域,内部函数仍然保持着原始作用域的引用(没有被回收),就会产生闭包。

      再看其他:

    function wait(num) {
            setTimeout(function timer(){
                console.log(num)
            }, 1000)
    }
    wait(1)

      这也是闭包,不管执行1秒后,timer 内部仍然引用着num, 因为 setTimeout 会持有对 timer 的引用,并没有 clear。所以 wait 内的作用域会保留下来。

      

    function click(value) {
            $(selector).click(function(){
                alert(value)
            })
    }
    
    click('hello')

      这也是闭包,因为 $(selector).click 绑定的函数内持有 click 的value,而 $(selector).click 执行后并没有解绑,所以为了保证正确运行,会保留 click 内的作用域。

    function outer () {
            var a = 2
            function inner () {
                console.log(a)
            }
            inner()
    }
    outer()

      上述代码,虽然函数内嵌了函数,但严格来讲并不是闭包。inner 执行后,并没有持有变量 a 的引用。outer 内的作用域可能已经被回收了。

    经典问题

    for (var i=1;i<=5;i++) {
            setTimeout(function timer(){
                console.log(i)
            }, i )
    }

      上述代码会打印 5 次 6。

      首先,javascript是单线程的,所以setTimeout(异步)会等for循环完了才会执行,而 for 循环完时, i 的值是6。但这并不足以导致setTimeout每次都打印出 6 。

      更重要的是每个(5个)setTimeout对 i 的引用,因为 for 中使用 var i 声明,所以 i 是在全局作用域内,每个setTimeout都引用了同一个 i。

      所以才会每次都打印出 6。

      相当于:

        var i = 6
    
        setTimeout(function timer(){
            console.log(i)
        }, i )
    
        setTimeout(function timer(){
            console.log(i)
        }, i )
    
        setTimeout(function timer(){
            console.log(i)
        }, i )
    
        // .....
    • 解决方案

        只要隔离每个setTimeout对 i 的引用即可。

        for (var i=1;i<=5;i++) {
            (function(){
                var j = i
                setTimeout(function timer(){
                    console.log(j)
                }, j )
            })()
        }
        //////////////////////////////////
        for (var i=1;i<=5;i++) {
            (function(j){
                setTimeout(function timer(){
                    console.log(j)
                }, j )
            })(i)
        }
        //////////////////////////////////
        for (let i=1;i<=5;i++) {
            setTimeout(function timer(){
                console.log(i)
            }, i )
        }

      上面三种本质都是隔离每个setTimeout对 i 的引用。IIFE内自成一个作用域,将 i 赋值给其他变量,就隔离了对 i 的引用了。

      而使用 let,虽然 let 没有用在 {...}内,但 let 在for头部中有特殊行为,这个行为会将 i 多次声明,每迭代一次,声明一次,每次迭代都会使用上一次迭代结束时的值来初始化 i 。

      相当于:

        // 这里块作用域指 IIFE 内的作用域
        {
            var j = 1
            setTimeout(function timer(){
                console.log(j)
            }, j )
        }
        // 这里块作用域指 IIFE 内的作用域
        {
            var j = 2
            setTimeout(function timer(){
                console.log(j)
            }, j )
        }
        // ......
    
        /////////////////////////////////
    
        {
            let i = 1
            setTimeout(function timer(){
                console.log(i)
            }, i )
        }
    
        {
            let i = 2
            setTimeout(function timer(){
                console.log(i)
            }, i )
        }
        // ......
    Welcome to my blog!
  • 相关阅读:
    CSPS_108
    TortoiseGit配置密钥的方法
    SLF4J和Logback和Log4j和Logging的区别与联系
    slf4j log4j logback关系详解和相关用法
    dubbo服务telnet命令的使用
    基于IDEA的JavaWeb开发环境搭建
    jdk8--十大新特性
    jdk8--collect总结
    字符串的排列
    调整数组顺序使奇数位于偶数前面
  • 原文地址:https://www.cnblogs.com/blogCblog/p/14450242.html
Copyright © 2020-2023  润新知