자바스크립트 언어를 심도 깊게 배우고 싶다면 클로저
라는 개념은 거의 피할 수 없습니다. 오늘은 를 함께 잘 살펴보겠습니다. 클로저 패키지란 정확히 무엇입니까? <br><strong>초보자라면 이전 기사를 먼저 읽어 보면 이 기사의 내용을 더 잘 이해할 수 있습니다.</strong><code>闭包
这个概念几乎是绕不开的关键,今天就让我们一起好好地盘一盘,闭包
到底是什么东西。
如果是零基础的小伙伴,可以先看看前一篇文章,帮助你更好的理解本文的内容:
我们先来看看闭包在MDN中的定义:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
闭包的定义非常晦涩难懂,如果通俗的解释一下的话,就是:
这么讲可能还是非常抽象,不过我们先有个概念,下面会详细解释这个概念。对于闭包,其实还有另一种解释:
区别于第一种解释,第二种解释把函数本身称作为闭包,这样的解释也合理,但更准确的话其实我们可以把这个函数称作闭包函数
。这样的话,其实两种解释都没有问题,也可以同时存在了。
看完了闭包的概念,我们通过实际的例子感受一下,闭包长什么样?闭包产生的条件?
function outer(){ var a=0; function inner(){ console.log(++a); } return inner; }var f=outer(); f(); //1f(); //2复制代码
我们先来看下这个例子里,闭包是如何体现的。我们首先定义了outer
函数,然后在outer
函数的内部声明了一个inner
函数并返回。
在外部,我们执行outer
函数,并将返回的inner
函数保存在了f
中,现在的f
就是我们说的闭包函数
了。
在例子中,我们执行了两次f
函数,分别输出了1
和2
,说明我们在执行f
函数中的console.log(++a);
语句时,根据我们之前对作用域的解释,会去函数所在的作用域链上寻找变量,最终在outer
函数中找到了这个变量,并进行了增值操作。
同时需要注意的是,我们输出的结果并不是两次1
,说明我们的作用域是共享的,可以看成outer
的作用域延伸到了外部。
outer
作用域本来应该在执行结束后释放,但根据GC
机制(我们这里先不展开介绍)会发现:
outer
了,但a
变量好像被引用了,而且外部还保存了起来不知道什么时候调用?好叭,那我就先不释放了。闭包就这样诞生了,在作用域消失之后,外部依然可以访问作用域中的变量。
我们根据刚刚的例子和解释总结一下,产生一个闭包需要哪些条件:
outer
就是外部函数,inner
就是返回的内部函数。inner
函数就引用了变量a
。outer
需要被执行,同时需要把返回的内部函数给保存起来,也就是我们例子中的var f=outer()
。只有同时满足了这三个条件,才能产生闭包。看似很难达成,事实上,我们实际代码中经常不经意间就能产生闭包。
通过刚刚的介绍,相信大家应该对闭包有了一个概念,接下来就让我们出发,深入的了解一下闭包的方方面面。
在刚刚的例子中,我们创建了一个闭包,执行多次闭包函数后,增加的都是同一个作用域中的变量a
🎜함수는 주변 상태(어휘 환경, 어휘 환경)에 대한 참조와 함께 번들로 제공됩니다(또는 함수는 참조로 둘러싸여 있습니다). 구성은 클로저입니다 🎜 blockquote>🎜클로저의 정의는 매우 모호합니다. 일반적인 방식으로 설명하면 다음과 같습니다. 🎜🎜아직 매우 추상적일 수 있지만 먼저 개념이 있고 이 개념에 대해서는 아래에서 자세히 설명하겠습니다. 클로저에 대해서는 실제로 또 다른 설명이 있습니다: 🎜
- 함수와 함수 내부에 정의된 위치 범위의 조합을 클로저라고 합니다.
🎜첫 번째 설명과 달리 두 번째 설명에서는 함수 자체를 클로저라고 부르는데, 이 설명도 타당하지만, 좀 더 정확하게 말하면 클로저라고 부를 수 있습니다. 함수를
- 내부 함수는 함수에서 선언되고 반환됩니다. 외부에서는 이 내부 함수를 통해 내부 값에 액세스할 수 있습니다.
클로저 함수
라고 합니다. 이 경우 실제로는 두 가지 설명 모두 문제가 없으며 동시에 존재할 수도 있습니다. 🎜2. 클로저를 느껴보세요
🎜 클로저의 개념을 읽어본 후 실제 예시를 통해 느껴볼까요? 폐쇄가 발생하는 조건은 무엇입니까? 🎜클로저는 어떤 모습인가요?
function outer() { var a = 0; function inner() { console.log(++a); } return inner; }var f1 = outer();var f2 = outer(); f1(); //1f2(); //1复制代码🎜먼저 이 예에서 클로저가 어떻게 반영되는지 살펴보겠습니다. 먼저outer
함수를 정의한 다음outer
함수 내부에inner
함수를 선언하고 이를 반환합니다. 🎜🎜외부적으로outer
함수를 실행하고 반환된inner
함수를f
에 저장합니다. 이제f가 됩니다. <code>클로저 함수
를 호출합니다. 🎜🎜예제에서는f
함수를 두 번 실행했는데1
과2
가 각각 출력되어를 실행 중임을 나타냅니다. f
함수에서console.log(++a);
문을 사용할 때 범위에 대한 이전 설명에 따라 함수가 있는 범위 체인에서 변수를 찾습니다. 찾았고 마지막으로 outer 함수에서 발견되어 값이 추가되었습니다. 🎜🎜동시에 우리가 출력하는 결과는1
이 두 번 발생하지 않는다는 점에 유의해야 합니다. 이는 우리 범위가 공유되고외부
의 범위로 볼 수 있음을 나타냅니다. 외부로 확장됩니다. 🎜🎜외부
범위는 원래 실행 후 해제되어야 하지만GC
메커니즘(여기에서는 소개하지 않음)에 따라 다음을 찾을 수 있습니다. 🎜🎜이러한 방식으로 클로저가 탄생했습니다. 범위가 사라진 후에도 범위의 변수는 외부에서 계속 액세스할 수 있습니다. 🎜
outer
를 해제할 준비가 되었는데,a
변수가 참조된 것 같고, 외부에도 저장되어 있어서 언제 호출해야 할지 모르겠습니다. 그것? 알았어, 그럼 아직 공개하지 않을게.클로저 생성 조건은 무엇인가요?
🎜위의 예제와 설명을 바탕으로 클로저를 생성하는 데 필요한 조건을 요약해 보겠습니다. 🎜🎜이 세 가지 조건이 동시에 충족되어야만 클로저가 생성될 수 있습니다. 달성하기 어려울 것 같지만 실제로 실제 코드에서 클로저가 실수로 생성될 수 있는 경우가 많습니다. 🎜🎜지금까지 소개를 통해 모든 사람이 클로저의 개념을 알아야 한다고 생각합니다. 다음으로 클로저의 모든 측면을 깊이 이해하도록 하겠습니다. 🎜
- 외부 함수에 내부 함수를 선언하고 내부 함수를 반환합니다.
여기서outer
는 외부 함수이고inner
는 반환된 내부 함수입니다.- 내부 함수는 외부 함수의 변수를 참조합니다.
이 예에서inner
함수는 변수a
를 참조합니다.- 외부 함수를 실행하고, 클로저를 생성하고, 반환된 내부 함수를 저장해야 합니다.
외부 함수outer
를 실행해야 하며 반환된 내부 함수(예제에서는var f=outer())를 저장해야 합니다. .코드>.
클로저를 자세히 경험해 보세요🎜
1. 여러 클로저 만들기
🎜지금 예시에서는 클로저, 클로저 함수를 여러 번 실행한 후 동일한 범위의 변수a
가 추가됩니다. 따라서 여러 클로저를 생성하려고 하면 어떻게 될까요?function outer() { var a = 0; function inner() { console.log(++a); } return inner; }var f1 = outer();var f2 = outer(); f1(); //1f2(); //1复制代码这段代码在刚刚的例子上进行了改动,我们执行了两次外部函数
outer
,并分别用不同的变量f1
和f2
保存。当执行f1
和f2
时会发现,输出的结果都是1
,说明f1
和f2
的作用域是独立的,f1
和f2
属于两个不同的闭包,我们用一张图来理解下:当分别创建
f1
和f2
时,调用了两次outer
函数,创建了两个不同的上下文。而当f1
和f2
分别执行时,根据作用域的查找规则,会去对应的作用域中查找变量,并执行增值输出,所以最终两个值均为2
;2.使用闭包实现模块化
我们知道,作用域的外部无法拿到作用域内部的值,而通过闭包,我们可以把作用域我们需要的值或者方法暴露出去,比如:
function outer() { var myNumber = 0; function addNumber() { myNumber++; } function getNumber() { return myNumber; } return { addNumber, getNumber } }var module = outer();module.addNumber();module.addNumber();module.getNumber();复制代码在这个例子中,我们同样定义了一个外部函数
outer
,另外还分别定义了两个函数addNumber
和getNumber
,用于增加和获取变量myNumber
。当我们执行
outer()
语句的时候,会创建一个上下文,同时把内部返回的对象保存在module
变量上,此时我们就创建了一个闭包,以及一个包含方法addNumber
和getNumber
的对象。由于外部是无法直接访问变量
myNumber
的,但由于闭包的原因,addNumber
和getNumber
是可以访问到这个变量的,因此我们成功的把变量myNumber
隐藏了起来,并且对外只提供了增加和获取myNumber
值的方法。试着用闭包解决问题
通过刚刚的例子,相信大家应该对闭包有了一定了解,接下来我们试着运用闭包来解决实际问题,先看一下例子:
for (var i = 0; i < 2; i++) { setTimeout(() => { console.log(i); }, 500); }复制代码这是一个十分容易令人误解的例子。接触过的小伙伴肯定都知道,最后会输出两次
2
而不是依次输出0
和1
,我们来看看为什么会这样。
首先,外部是一个for
循环,执行了两次语句。for (var i = 0; i < 2; i++) { ... // 执行两次}复制代码在函数的内部,我们调用了
setTimeout
函数,关键的地方来了,这个函数是一个异步函数,并不会马上执行,所以实际上等外部的for
循环执行结束了,才会真的执行setTimeout
中的函数。还有第二个关键点在于,在for
循环中,var
定义的变量相当于定义在全局,而不存在块级作用域。那么刚刚的代码就可以近似的看成这样了。var i=0; i++; //i=1i++; //i=2console.log(i);console.log(i);复制代码非常直接粗暴,但可以很清晰的看出输出结果为何是两次
2
了,因为大家共用了同一个作用域,i
的值被覆盖了。那么知道了问题出在哪里,我们试着用上我们刚刚学习的闭包,来创建不同的作用域:for (var i = 0; i < 2; i++) { function outer() { var j = i; setTimeout(function inner() { console.log(j); }, 500); } outer(); }复制代码我们按照闭包的样式对刚刚的代码进行了改造,这里的
setTimeout
并不直接就是inner
函数,这是因为它在这里起到了定义inner
函数,并保存执行inner
函数的功能。我们可以看到,最终结果依次输出了
0
和1
,说明我们的闭包是成功了的,但这样的闭包比较臃肿,我们试着提高一下,写的更加优雅一点:for (var i = 0; i < 2; i++) { (function() { //用自执行函数代替了`outer`函数的定义和执行两个步骤 var j = i; setTimeout(function inner() { console.log(j); }, 500); })(); }复制代码还可以再简化一下:
for (var i = 0; i < 5; i++) { for (var i = 0; i < 2; i++) { (function(j) {//用自执行函数代替了`outer`函数的定义和执行两个步骤 setTimeout(function inner() { console.log(j); }, 500); })(i);//var j=i的步骤,通过传入i值替换 } }复制代码这样就大功告成了!
总结
本篇首先介绍了闭包的定义以及不同人对闭包的理解,之后介绍了闭包产生的原因并总结了三个条件,之后举例说明了如何创建多个闭包和通过闭包实现模块,最后讲述了如何通过闭包解决for循环中使用异步函数依次输出值的问题。其实闭包没有想象的那么可怕,只要你愿意静下心去探索去了解,闭包也会对你敞开大门~
写在最后
都看到这里了,如果觉得对你有帮助的话不妨点个赞支持一下呗~
以后会陆续更新更多文章和知识点,感兴趣的话可以关注一波~
如果哪里有错误的地方或者描述不准确的地方,也欢迎大家指出交流~
相关免费学习推荐:javascript(视频)
위 내용은 JavaScript의 클로저에 대해 자세히 살펴보겠습니다!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!