闭包是是纯函式语言的一个特性,也是JS的一个关键性的特色,虽然不了解也能开发程序,但我们不是这种人对吧?

 

闭包不仅可以减少某些高阶功能的代码数量和复杂度,并且可以让我们做到原本无法做的复杂功能。听到这还不想认识他吗!

 

那什么是闭包呢?它是一种数据结构,可以说是一种技术,能记住函式及函式被建立时当下环境,也就是说函式可以存取在建立时作用范围内的变数(jmcintoshcc)。

 

我们先来看一个最简单的示例:

 

var outer =“global”;

function outerFun(){

console.log(outer);

}

outerFun();//“global”

你可能会疑问这是闭包吗?平常就是这样用啊!

 

我们在同一个作用范围(也就是全局)中宣告一个变数outer和函式outerFun,并呼叫函式。

这个范围就是一个闭包,只是在程序结束时永远不会消失,让你没有感觉而已。

 

在JS中每当函式被建立时,一个闭包就会产生。虽然很多人经常拿巢状函式来作为示例来说明闭包,但并不是只有它才能产生,要记住这点!

 

虽然这么说,但我们还是会以巢状函式来举例,感觉有点打自己脸….

 

function outerFun(x){

function innerFun(){

x++

console.log(x);

}

return innerFun;

}

var num = outerFun(1);

num();//2

num();//3

在执行后会发现我的x怎么每一次执行都会在加一呢?就是因为闭包造成的。

 

它们在函式建立时为函式及作用范围内的变数建立了一个类似「安全气泡」的东西,让我们在执行时可以使用。

 

刚刚的代码,还可以简短成这样:

 

function outerFun(x){

return()=>{

x++;

console.log(x);

}

}

var num = outerFun(1);

num();//2

num();//3

不过这边有几点要先各位说明,避免产生误会:

 

函式呼叫和函式建立是两回事,在outerFun函式呼叫后,num才是函式建立,这时候num的闭包结构才会产生

闭包不是只会产生在巢状(内部)函式的回传时。所有函式在建立时都会产生闭包。

我们也可以透过下断点的方式观察闭包中的值:

 


所以我到底可以用哪些外部变数呢?基本上一个内部函式可以有三个作用域:

 

自已本身的

外部函式的

全局的

内部函式可以看到(或存取)外部函式,而形成一个Scope Chain(作用域连锁)。

 

这边我们来举一个实际的例子:

我们先建立两个按钮和一个显示区

 

<button id=“btn”>Click one</button>

<button id=“btn2”>Click two</button>

<div id=“display”></div>

然后写一个计数器的函式,并挂上监听事件(artwithbite)

 

var btn = document.getElementById('btn');

var btn2 = document.getElementById('btn2');

var display = document.getElementById('display');

 

function recordClick(name){

let count = 0;

return()=>{

count++;

console.log(count);

display.innerHTML = `${name} count:${count}`;

}

}

 

btn.addEventListener(“click”,recordClick(“Click one”));

btn2.addEventListener(“click”,recordClick(“Click two”));

执行后可以发现到两个按钮并不会有共享一个count问题,这是因为利用闭包产生了一个类似私有变数的功能。

这边附上codepen示例给大家玩玩。

 

 

那这边可能大家会有一个问题,到底闭包是复制了这些值还是只是参照而已呢?

答案是参照,最常拿来解说的就是利用非同步回呼函式来举例:

 

function counter(){

let i = 0;

for(i = 0;i< 5;i++){

setTimeout(function(){

console.log('counter is ' + i)

},1000);

}

}

counter();

可能很多人认为说结果会是1,2,3,4,5但是实际印出来怎么都是5?

 

这是因为「闭包结构中所记忆的环境值是用参照指向的」,setTimeout会先到队列中准备延迟执行,等到回来主程式时,循环早就执行完了,i也已经变成了5,要解决这个问题其实可以这样解:

 

function timer(index){

return setTimeout(function(){

console.log('counter is ' + index)

},1000);

}

function counter(){

let i = 0;

for(i = 0;i< 5;i++){

timer(i);

}

}

counter();

//counter is 0

//counter is 1

//counter is 2

//counter is 3

//counter is 4

创建一个函式timer让循环中的i值传入并形成一个闭包,因为每次传入timer的值都不同,所以每个setTimeout闭包中的值也会不同。

 

我们也可以这样解:

 

function showIndex(index){

return()=> console.log(index);

}

function counter(){

let i = 0;

for(i = 0;i< 5;i++){

setTimeout(showIndex(i),1000);

}

}

counter();

那么,以上就是闭包的用法,一样如果有错误及来源未附上也欢迎留言指正,那么我们明天见(recordcompanystartup)。