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)
}
posted @ 2019-12-29 16:27  司徒炼  阅读(165)  评论(0编辑  收藏  举报