zoukankan      html  css  js  c++  java
  • JavaScript的闭包

    闭包

    主要了解如下

    • 闭包的原理
    • 闭包的创建
    • 闭包的优缺点及其用处
    • 释放闭包
    • 循环中的闭包

    闭包通俗来讲就是在一个函数外部能调用这个函数内部的函数,这种情况就是使用了闭包,不过更多时候是自己已经使用了闭包,但并不知道那就是闭包。

    function foo(){
        var a = 1;
        function bar(){
            console.log(a);
        }
    }
    

    就如同上面这段代码,我们想要在foo外部调用bar,甚至想在外部访问foo里面的变量,根据javaScript的作用域来看,正常情况是不可能的,函数外部是不能访问函数内部的变量的,而且这个函数执行完后就会被销毁了,所以针对这种情况下就要使用闭包了。

    闭包的原理

    从逻辑上看,就是要让函数外部能够调用函数内部的函数,然而,函数执行完后又会被销毁,这种时候就要想办法让函数不被销毁,怎么才能不被销毁,只要有变量一直引用它就行了。

    比如我们可以把内部定义的函数返回给函数外部的变量,这时候外部的变量就可以调用里面的函数了

    function foo(){
        var a = 1;
        function bar(){
            console.log(a);
        }
        
        return bar;
    }
    
    var baz = foo();
    

    就如同上面代码这样,由于baz有对bar的引用,而foo的作用域由于有资源被引用,因此不会被销毁,而bar也可以访问foo里定义的变量,如此闭包就创建了。

    创建闭包

    最简单的创建方法就是在函数内部返回一个函数

    代码1

    function foo(){
    	var a = 1;
    	return function bar(){
    		++a;
    		console.log(a)
    	}
    }
    var bar = foo();
    bar() // 2
    bar() // 3
    bar() // 4
    

    当然也有其他的方法

    代码2

    var fn;
    function foo(){
    	var a = 2;
    	function baz(){
    		console.log(a);
    	}
    	fn = baz;
    }
    
    function bar(){
    	fn(); // 这就是闭包
    }
    
    foo();
    
    bar(); // 2
    
    

    代码3

    function foo(){
    	var a = 2;
    	function baz(){
    		console.log(a);
    	}
    	
    	bar(baz)
    }
    
    function bar(fn){
    	fn(); // 这就是闭包
    }
    
    foo();
    

    上面的三段代码都创建了闭包,有什么相同之处?他们都是在函数的外部,调用某个函数内部的函数,这就是闭包。

    也就是所,无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会保持对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包

    好吧,或许这时候还是不明白什么时候悄悄的使用了闭包,来看下面这段代码

    代码4

    function waitLog(mes){
        setTimeout(function time(){
        	console.log(mes)    
        }, 1000)
    }
    waitLog('hello world');
    

    我相信在实际编程中或多或少会用到定时器,这也是使用了闭包,在引擎内部肯定内置了一个工具函数setTimeout(...),这个函数有一个参数,比如fn或func,总之它是用来接收我们传递的time()这个函数,之后经过内部的一些算法,最终调用了这个fn或func,反馈给我们的效果就是延迟了几秒后再执行函数。

    这段代码4和代码3,其实及其相似,把代码3修改下

    function foo(mes){
    	bar(function baz(){
    		console.log(mes);
    	})
    }
    //把 bar 看成 引擎内置的函数
    function bar(fn){
    	fn(); // 这就是闭包
    }
    foo("hello world");
    

    bar看成是setTimeout,是不是挺相似的。

    再看着段jq代码

    function fn(select, mes){
        $(select).click(function(){
            console.log(mes)
        })
    }
    fn("#btn1", "hello")
    fn("#btn2", "world")
    

    这段代码也在使用闭包,这几段代码有什么特点吗?它们都是使用了回调函数,当我们把一个函数作为参数传递给另一个参数时,其实就是使用了回调函数,然而,使用了回调函数,就是在使用闭包

    现在应该知道闭包如何创建了,又在什么情况下使用了闭包。

    闭包的优缺点

    优点

    • 避免全局变量污染
    • 变量长期在内存中
    • 可以模拟私有变量

    缺点

    • 内存泄露
    • 性能降低

    用处

    • 在外部操作函数内部变量
    • 变量始终保持在内存中,不会函数执行后就被销毁

    释放闭包

    由于创建闭包后,会一直被引用,内存一直存在,不会被释放,所以在使用完闭包后,要手动释放闭包

    function foo(){
    	var a = 1;
    	return function bar(){
    		++a;
    		console.log(a)
    	}
    }
    var bar = foo();
    bar() // 2
    bar() // 3
    bar() // 4
    
    bar = null; // 释放闭包
    
    //重新创建闭包
    var bar = foo();
    bar() // 2
    bar() // 3
    bar() // 4
    

    循环中的闭包

    还是一个比较经典的代码

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

    上面代码并没有自己想要的结果1,2,3,4,5 ,而是一次性输出了5个6,首先,回调函数是在循环结束后再执行的,所以哪怕是把1000,换成0,依然是循环后再执行。

    至于为什么没有一次性输出1,2,3,4,5,而是6,这与作用域有关,因为var实际上是函数作用域,此时更是全局作用域,这5次循环的变量i,实际上都是同一个,因此循环结束后,i已经是6了,这个时候才执行回调函数,所以会一次性输出5个6。

    为了一次性输出1,2,3,4,5,就要让每次循环都有一个独立的i,所以只要使用块级作用域就行了,也就是let

    for(let i = 1; i<=5; ++i){
    	setTimeout(function time(){
    		console.log(i)
    	}, 1000)
    }
    
  • 相关阅读:
    gentoo Wireless Configuration
    Gentoo: Chrome
    Gentoo: user's permission
    参考
    GithubPages上部署hexo
    hexo 个人博客搭建
    服务器修改用户名和密码
    CentOS 服务器搭建 mediawiki
    mysql 修复命令日志
    个体如何采用敏捷的工作方式
  • 原文地址:https://www.cnblogs.com/tourey-fatty/p/12115372.html
Copyright © 2011-2022 走看看