zoukankan      html  css  js  c++  java
  • 浅谈JavaScript闭包

    闭包

    什么是闭包

    • 闭包其实就是一个可以访问其它函数内部变量的函数
    • 从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链
    • 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行
    function foo() {
        var name = 'xcc'
        return function() {
            console.log(name)
        }
    }
    foo()
    var result = foo()
    result()    // xcc
    

    什么时候构成闭包

    • 闭包产生的本质:当前环境中存在指向父级作用域的引用(案例1)
    • 只要父级作用域的引用存在既可(案例2)

    案例1中console可以访问到的作用域有func2,func1和window

    // 案例1
    function foo() {
        var name = 'xcc'
        function func2() {
            console.log(name)
        }
        return func2
    }
    foo()
    var result = foo()
    result() 
    
    // 案例2
    var func2
    function func1() {
        var a = 2
        func2 = function() {
            console.log(a)    // 2
        }
    }
    

    作用域链

    • 当访问一个变量时,代码解释器会先在当前作用域中查找,如果没有找到,就去父级作用域去查找,知道找到该变量或者不存在于父作用域中,这个链路就是作用域链

    查找过程:函数func2内部作用域 -> 函数func1作用域 -> 全局作用域

    var a = 1
    function func1() {
        var b = 2
        function func2() {
            var c = 3
            console.log(a)    // 1
        }
    }
    

    闭包的实现方法

    • 在函数内部返回一个函数
    • 在定时器、事件监听、ajax请求、web workers或者任何一部中,只要使用了回调函数,实际上就是在使用闭包(案例3)
    • 作为函数参数传递的形式(案例4)
    • IIFE(立即执行函数),创建了闭包,保存了全局作用域和当前函数的作用域,因此可以输出全局的变量(案例5)
      • IIFE拥有独立的作用域,不会污染全局作用域
    // 案例3
    // 定时器
    setTimeout(function foo() {
        console.log(1)
    }, 1000)
    // 事件监听
    $('#app').click(function() {
        console.log('event')
    })
    
    // 案例4
    var a = 1
    function foo() {
        var a = 2
        function baz() {
            console.log(a)
        }
        bar(baz)
    }
    
    function bar(fn) {
        // 闭包
        fn()
    }
    foo()
    
    // 案例5
    var name = 'xcc'
    (function foo() {
        console.log(name)    // xcc
    })()
    

    闭包的优缺点

    优点

    • 局部作用域
    • 减少全局变量污染

    缺点

    • 由于闭包会使一些变量一直保存在内存中不会自动释放,所以如果大量使用的话会消耗大量内存,在IE9之前闭包会导致内存泄漏

    经典问题

    案例5中,console.log会打印出5个6出来,如果想实现输出1、2、3、4、5、6应该怎么办?

    // 案例6
    for(var i = 1; i < 6; i++) {
        setTimeout(function () {
         console.log(i)    // 5
        }, 0)
    }
    
    • setTimeout为宏任务,由于JavaScript中单线程eventLoop机制,在主线程同步执行完后才会去执行宏任务,因此循环结束后setTimeout中回调才依次执行
    • 因为setTimeout函数也是一种闭包,往上找它的父级作用域就是window,变量i为window上的全局变量

    IIFE

    for(var i = 1; i < 6; i++) {
        (function(j) {
            setTimeout(function () {
             console.log(j)    // 1、2、3、4、5
            }, 0)
        })(i)
    }
    

    let块级作用域

    for(let i = 1; i < 6; i++) {
        setTimeout(function () {
          console.log(i)    // 1、2、3、4、5
        }, 0)
    }
    

    定时器传入第三个参数

    for(let i = 1; i < 6; i++) {
        setTimeout(function (j) {
          console.log(j)    // 1、2、3、4、5
        }, 0, i)
    }
    

    参考资料

  • 相关阅读:
    JavaScript数字和字符串转换示例
    Angular CLI 使用教程指南参考
    angular2 post以“application/x-www-form-urlencoded”形式传参的解决办法
    Arison [JS]window.location获取url各项参数详解
    $.ajaxComplete()
    angular2 编写公用组件
    获取本周、本季度、本月、上月的开端日期、停止日期
    Angular2
    轮播图插件 SuperSlide2.1滑动门jQuery插件
    书写Css文件要点
  • 原文地址:https://www.cnblogs.com/sk-3/p/14702836.html
Copyright © 2011-2022 走看看