Heim > Artikel > Web-Frontend > Werfen wir einen genaueren Blick auf Verschlüsse in JavaScript!
Wenn Sie die Sprache von JavaScript gründlich erlernen möchten, ist das Konzept von closure
fast unvermeidlich. Lassen Sie uns heute gemeinsam einen genauen Blick darauf werfen, Verschluss Was genau ist ein Paket? <br><strong>Wenn Sie ein Anfänger sind, können Sie zuerst den vorherigen Artikel lesen, um den Inhalt dieses Artikels besser zu verstehen:</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
🎜Eine Funktion wird mit einer Referenz auf ihren umgebenden Zustand (lexikalische Umgebung, lexikalische Umgebung) gebündelt (oder die Funktion ist von Referenzen umgeben), wie folgt: Zusammensetzung ist Abschluss 🎜 blockquote>🎜Die Definition von „Abschluss“ ist sehr unklar. Wenn man sie auf populäre Weise erklärt, lautet sie: 🎜🎜Das mag noch sehr abstrakt sein, aber wir haben zunächst ein Konzept, und dieses Konzept wird im Folgenden ausführlich erläutert. Für Abschlüsse gibt es tatsächlich eine andere Erklärung: 🎜
- Eine Funktion und ein Ort, die innerhalb einer Funktion definiert sind. Die Kombination von Bereichen wird als Abschluss bezeichnet.
🎜Anders als die erste Erklärung nennt die zweite Erklärung die Funktion selbst auch einen Abschluss, aber um genauer zu sein, können wir sie tatsächlich als Abschluss bezeichnen Diese Funktion wird als
- Eine interne Funktion wird in einer Funktion deklariert und zurückgegeben. Über diese interne Funktion können wir auf den internen Wert zugreifen
Abschlussfunktion
bezeichnet. In diesem Fall gibt es eigentlich kein Problem mit beiden Erklärungen und sie können gleichzeitig nebeneinander existieren. 🎜2. Fühlen Sie den Abschluss
🎜Nachdem wir das Konzept des Abschlusses gelesen haben, wollen wir ihn anhand konkreter Beispiele erspüren. Was sind die Voraussetzungen für eine Schließung? 🎜Wie sieht ein Abschluss aus?
function outer() { var a = 0; function inner() { console.log(++a); } return inner; }var f1 = outer();var f2 = outer(); f1(); //1f2(); //1复制代码🎜Schauen wir uns zunächst an, wie sich der Abschluss in diesem Beispiel widerspiegelt. Wir definieren zuerst die Funktionouter
, deklarieren dann eine Funktioninner
innerhalb der Funktionouter
und geben sie zurück. 🎜🎜Extern führen wir die Funktionouter
aus und speichern die zurückgegebene Funktioninner
inf
Wir rufenAbschlussfunktion
auf. 🎜🎜Im Beispiel haben wir die Funktionf
zweimal ausgeführt und1
bzw.2
ausgegeben, was anzeigt, dass wirf Wenn Sie die Anweisung <code>console.log(++a);
in einer Funktion verwenden, suchen wir gemäß unserer vorherigen Erklärung des Bereichs nach Variablen in der Bereichskette, in der sich die Funktion befindet. und schließlich in outer gefunden und der Wert hinzugefügt. 🎜🎜Gleichzeitig ist zu beachten, dass das von uns ausgegebene Ergebnis nicht zweimal1
ist, was darauf hinweist, dass unser Bereich gemeinsam genutzt wird und als Bereich vonouter
angesehen werden kann erstreckt sich nach außen. 🎜🎜Deräußere
-Bereich sollte ursprünglich nach der Ausführung freigegeben werden, aber gemäß demGC
-Mechanismus (wir werden ihn hier nicht vorstellen) finden wir: 🎜Ich bin bereit,
🎜Der Abschluss wurde auf diese Weise geboren. Nachdem der Bereich verschwindet, kann weiterhin von außen auf die Variablen im Bereich zugegriffen werden. 🎜outer
freizugeben, aber die Variablea
scheint referenziert zu sein und ich weiß nicht, wann ich sie aufrufen soll Es? Okay, dann werde ich es noch nicht veröffentlichen.Was sind die Bedingungen für die Abschlussgenerierung?
🎜Basierend auf den gerade gegebenen Beispielen und Erklärungen fassen wir zusammen, welche Bedingungen erforderlich sind, um einen Abschluss zu generieren: 🎜🎜Nur wenn diese drei Bedingungen gleichzeitig erfüllt sind, kann ein Abschluss generiert werden. Es scheint schwierig zu sein, dies zu erreichen, aber tatsächlich können Abschlüsse in unserem eigentlichen Code oft versehentlich generiert werden. 🎜🎜Durch die Einleitung bin ich der Meinung, dass jeder ein Konzept für den Abschluss haben sollte. Als nächstes wollen wir uns daran machen, alle Aspekte des Abschlusses eingehend zu verstehen. 🎜
- Deklarieren Sie eine interne Funktion in der externen Funktion und geben Sie die interne Funktion zurück.
Hier istouter
die externe Funktion undinner
die zurückgegebene interne Funktion.- Die innere Funktion referenziert die Variablen in der äußeren Funktion.
In unserem Beispiel bezieht sich die Funktioninner
auf die Variablea
.- Die externe Funktion muss ausgeführt werden, ein Abschluss wird erstellt und die zurückgegebene interne Funktion muss gespeichert werden.
Die externe Funktionouter
muss ausgeführt werden und die zurückgegebene interne Funktion muss gespeichert werden, in unserem Beispielvar f=outer() .Code>.
Erleben Sie Abschlüsse im Detail🎜
1. Erstellen Sie mehrere Abschlüsse
🎜Im Beispiel gerade haben wir Nach dem Erstellen eines Beim mehrmaligen Ausführen der Abschlussfunktion werden die Variablena
im selben Bereich hinzugefügt. Was passiert also, wenn wir versuchen, mehrere Abschlüsse zu erstellen: 🎜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(视频)
Das obige ist der detaillierte Inhalt vonWerfen wir einen genaueren Blick auf Verschlüsse in JavaScript!. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!