Heim >Web-Frontend >js-Tutorial >Front-End Advanced (4): Detaillierte Darstellung der Scope-Kette und des Abschlusses
Als ich neu im Erlernen von JavaScript war, habe ich beim Erlernen von Abschlüssen viele Umwege gemacht . . Um dieses Mal das Grundwissen zu klären, ist es auch eine große Herausforderung, den Abschluss klar zu erklären.
Wie wichtig sind Schließungen? Wenn Sie neu im Front-End sind, kann ich Ihnen nicht intuitiv sagen, wie allgegenwärtig Abschlüsse in der tatsächlichen Entwicklung sind, aber ich kann Ihnen sagen, dass Sie in Front-End-Interviews nach Abschlüssen fragen müssen. Interviewer nutzen häufig ihr Verständnis von Abschlüssen, um das grundlegende Niveau des Interviewers zu bestimmen. Konservative Schätzungen gehen davon aus, dass mindestens 5 von 10 Front-End-Interviewern an Abschlüssen sterben.
Aber warum sind Schließungen so wichtig, obwohl so viele Menschen es immer noch nicht verstehen? Liegt es daran, dass nicht alle lernen wollen? Das ist wirklich nicht der Fall, aber die meisten chinesischen Artikel, die Schließungen erklären, die wir über die Suche gefunden haben, erklären Schließungen nicht klar. Entweder ist es oberflächlich, es ist undurchschaubar oder es ist einfach Unsinn. Ich selbst habe einmal eine Zusammenfassung über Schließungen geschrieben. Rückblickend konnte ich es nicht ertragen, sie anzusehen [Gesichtsbedeckung].
Daher besteht der Zweck dieses Artikels darin, den Abschluss klar und deutlich zu erklären, damit der Leser den Abschluss nach dem Lesen vollständig verstehen kann, anstatt ihn nur halb zu verstehen.
Bevor ich die Scope-Kette im Detail erkläre, gehe ich davon aus, dass Sie die folgenden wichtigen Konzepte in JavaScript grob verstanden haben. Diese Konzepte werden sehr hilfreich sein.
Speicherplatz
Garbage-Collection-Mechanismus
Ausführungskontext
Wenn Sie es noch nicht verstanden haben, können Sie die ersten drei Artikel dieser Serie lesen. Am Ende dieses Artikels befindet sich ein Link zum Inhaltsverzeichnis. Um Abschlüsse zu erklären, habe ich das Grundwissen für alle aufbereitet. Haha, was für eine große Show.
Bereich
In JavaScript können wir den Bereich als eine Reihe von Regeln definieren, die verwendet werden, um zu verwalten, wie die Engine auf der Variablensuche basiert Bezeichnername im aktuellen Bereich und in verschachtelten Unterbereichen.
Der Bezeichner bezieht sich hier auf Variablenname oder Funktions-Name
JavaScript Es gibt nur globaler Geltungsbereich und Funktionsumfang (da eval in unserer täglichen Entwicklung selten verwendet wird, werden wir hier nicht darauf eingehen).
Umfang und Ausführungskontext sind zwei völlig unterschiedliche Konzepte. Ich weiß, dass viele Leute sie verwechseln, aber achten Sie darauf, sorgfältig zu unterscheiden.
Der gesamte Ausführungsprozess von JavaScript-Code ist in zwei Phasen unterteilt: die Codekompilierungsphase und die Codeausführungsphase. Die Kompilierungsphase wird vom Compiler abgeschlossen, der den Code in ausführbaren Code übersetzt. In dieser Phase werden die Bereichsregeln festgelegt. Die Ausführungsphase wird von der Engine abgeschlossen. Die Hauptaufgabe besteht darin, ausführbaren Code auszuführen. Der Ausführungskontext wird in dieser Phase erstellt.
Umfangskette
Review Let's Werfen Sie einen Blick auf den Lebenszyklus des Ausführungskontexts, den wir im vorherigen Artikel analysiert haben, wie unten gezeigt.
Wir haben festgestellt, dass die Bereichskette während der Erstellungsphase des Ausführungskontexts generiert wird. Das ist seltsam. Wir haben oben gerade gesagt, dass der Bereich die Regeln in der Kompilierungsphase bestimmt, aber warum wird die Bereichskette in der Ausführungsphase bestimmt?
Der Grund, warum wir diese Frage haben, ist, dass jeder ein Missverständnis über Umfang und Umfangskette hat. Wie oben erwähnt, handelt es sich beim Bereich um eine Reihe von Regeln. Was ist also eine Bereichskette? Es handelt sich um die konkrete Umsetzung dieses Regelwerks. Das ist also die Beziehung zwischen Umfang und Umfangskette. Ich glaube, jeder sollte sie verstehen.
Wir wissen, dass eine Funktion, wenn sie aufgerufen und aktiviert wird, mit der Erstellung des entsprechenden Ausführungskontexts beginnt. Während des Generierungsprozesses des Ausführungskontexts werden jeweils das Variablenobjekt, die Bereichskette und der Wert davon bestimmt . In einem früheren Artikel haben wir variable Objekte ausführlich erläutert, und hier werden wir Bereichsketten ausführlich erläutern.
Die Bereichskette besteht aus einer Reihe variabler Objekte in der aktuellen Umgebung und der oberen Umgebung. Sie gewährleistet den ordnungsgemäßen Zugriff der aktuellen Ausführungsumgebung auf Variablen und Funktionen, die den Zugriffsberechtigungen entsprechen.
Um allen das Verständnis der Scope-Kette zu erleichtern, möchte ich sie zunächst anhand eines Beispiels und des entsprechenden Diagramms veranschaulichen.
var a = 20; function test() { var b = a + 10; function innerTest() { var c = 10; return b + c; } return innerTest(); } test();
Im obigen Beispiel werden der Ausführungskontext von global, Funktionstest und Funktion innerTest nacheinander erstellt. Wir legen ihre variablen Objekte als VO(global), VO(test) bzw. VO(innerTest) fest. Die Bereichskette von innerTest enthält auch diese drei variablen Objekte, sodass der Ausführungskontext von innerTest wie folgt ausgedrückt werden kann.
innerTestEC = { VO: {...}, // 变量对象 scopeChain: [VO(innerTest), VO(test), VO(global)], // 作用域链 this: {} }
Ja, Sie haben richtig gelesen, wir können ein Array direkt verwenden, um die Bereichskette darzustellen. Das erste Element des Arrays, ScopeChain[0], ist das Ende Die Bereichskette Das vordere Ende und das letzte Element des Arrays ist das Ende der Bereichskette, und alle Enden sind globale Variablenobjekte.
Viele Menschen missverstehen möglicherweise falsch, dass der aktuelle Geltungsbereich und der obere Geltungsbereich eine umfassende Beziehung haben, aber das ist nicht der Fall. Ich denke, eine Einbahnstraße, die am vorderen Ende beginnt und am Ende endet, ist eine passendere Beschreibung. Wie im Bild gezeigt.
Beachten Sie, dass dies der Fall ist, da das variable Objekt aktiv wird, wenn der Ausführungskontext in die Ausführungsphase eintritt wurde im vorherigen Artikel erwähnt, daher wird AO verwendet, um es in der Abbildung darzustellen. Aktives Objekt
Ja, die Bereichskette besteht aus einer Reihe variabler Objekte. Wir können das -Variablenobjekt in dieser Einweg-Kanalkennung abfragen , sodass Sie auf Variablen im oberen Bereich zugreifen können.
Für diejenigen, die ein wenig Erfahrung mit JavaScript haben, aber das Konzept von Abschlüssen nie wirklich verstanden haben, kann das Verständnis von Abschlüssen in gewisser Weise als eine Wiedergeburt angesehen werden Ein Engpass kann Ihre Fähigkeiten erheblich verbessern.
Abschlüsse stehen in engem Zusammenhang mit Bereichsketten.
Abschlüsse werden während der Funktionsausführung bestätigt.
Werfen Sie zunächst die Definition des Abschlusses direkt weg: Ein Abschluss wird generiert, wenn sich eine Funktion an den Bereich erinnern und darauf zugreifen kann, in dem sie sich befindet (mit Ausnahme des globalen Bereichs), auch wenn der Die Funktion wird außerhalb des aktuellen Bereichs ausgeführt.
Um es einfach auszudrücken: Angenommen, Funktion A ist in Funktion B definiert und wenn Funktion A ausgeführt wird, greift sie auf das variable Objekt in Funktion B zu, dann ist B ein Verschlussbeutel.
In Basic Advanced (1) habe ich den Garbage-Collection-Mechanismus von JavaScript zusammengefasst. JavaScript verfügt über einen automatischen Garbage-Collection-Mechanismus. In Bezug auf den Garbage-Collection-Mechanismus gibt es ein wichtiges Verhalten: Wenn ein Wert seine Referenz im Speicher verliert, wird er vom Garbage-Collection-Mechanismus anhand eines speziellen Algorithmus gefunden . Und recyceln Sie es, um Speicher freizugeben.
Und wir wissen, dass nach der Ausführung des Ausführungskontexts der Funktion der Lebenszyklus endet und der Ausführungskontext der Funktion dann seine Referenz verliert. Der von ihm belegte Speicherplatz wird bald vom Garbage Collector freigegeben. Die Existenz von Schließungen wird diesen Prozess jedoch verhindern.
Beginnen wir mit einem einfachen Beispiel.
var fn = null; function foo() { var a = 2; function innnerFoo() { console.log(a); } fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn } function bar() { fn(); // 此处的保留的innerFoo的引用 } foo(); bar(); // 2
Im obigen Beispiel endet nach der Ausführung von foo()
dem gesunden Menschenverstand zufolge der Lebenszyklus der Ausführungsumgebung und der belegte Speicher wird vom Garbage Collector freigegeben. Aber durch fn = innerFoo
bleibt der Verweis auf die Funktion innerFoo erhalten und wird in die globale Variable fn kopiert. Dieses Verhalten führt dazu, dass das variable Objekt von foo beibehalten wird. Wenn daher die Funktion fn in der Funktionsleiste ausgeführt wird, kann weiterhin auf das beibehaltene Variablenobjekt zugegriffen werden. Daher kann in diesem Moment noch auf den Wert der Variablen a zugegriffen werden.
Auf diese Weise können wir foo als Abschluss bezeichnen.
Die folgende Abbildung zeigt die Umfangskette des Verschlusses foo.
Wir können ihn in finden chrSehen Sie sich den Funktionsaufrufstapel und die Generierung der Bereichskette an, die generiert werden, wenn dieser Code in den Entwicklertools des Ome-Browsers ausgeführt wird. Wie unten gezeigt.
Weitere Informationen zum Beobachten von Schließungen in Chrome und weitere Beispiele für Schließungen finden Sie in der Basic Series (6)
在上面的图中,红色箭头所指的正是闭包。其中Call Stack为当前的函数调用栈,Scope为当前正在被执行的函数的作用域链,Local为当前的局部变量。
所以,通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量。比如在上面的例子中,我们在函数bar的执行环境中访问到了函数foo的a变量。个人认为,从应用层面,这是闭包最重要的特性。利用这个特性,我们可以实现很多有意思的东西。
不过读者老爷们需要注意的是,虽然例子中的闭包被保存在了全局变量中,但是闭包的作用域链并不会发生任何改变。在闭包中,能访问到的变量,仍然是作用域链上能够查询到的变量。
对上面的例子稍作修改,如果我们在函数bar中声明一个变量c,并在闭包fn中试图访问该变量,运行结果会抛出错误。
var fn = null; function foo() { var a = 2; function innnerFoo() { console.log(c); // 在这里,试图访问函数bar中的c变量,会抛出错误 console.log(a); } fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn } function bar() { var c = 100; fn(); // 此处的保留的innerFoo的引用 } foo(); bar();
关于这一点,很多同学把函数调用栈与作用域链没有分清楚,所以有的大神看了我关于介绍执行上下文的文章时就义正言辞的说我的例子有问题,而这些评论有很大的误导作用,为了帮助大家自己拥有能够辨别的能力,所以我写了基础(六),教大家如何在chrome中观察闭包,作用域链,this等。当然我也不敢100%保证我文中的例子就一定正确,所以教大家如何去辨认我认为才是最重要的。
闭包的应用场景
接下来,我们来总结下,闭包的常用场景。
我们知道setTimeout的第一个参数是一个函数,第二个参数则是延迟的时间。在下面例子中,
function fn() { console.log('this is test.') } var timer = setTimeout(fn, 1000); console.log(timer);
执行上面的代码,变量timer的值,会立即输出出来,表示setTimeout这个函数本身已经执行完毕了。但是一秒钟之后,fn才会被执行。这是为什么?
按道理来说,既然fn被作为参数传入了setTimeout中,那么fn将会被保存在setTimeout变量对象中,setTimeout执行完毕之后,它的变量对象也就不存在了。可是事实上并不是这样。至少在这一秒钟的事件里,它仍然是存在的。这正是因为闭包。
很显然,这是在函数的内部实现中,setTimeout通过特殊的方式,保留了fn的引用,让setTimeout的变量对象,并没有在其执行完毕后被垃圾收集器回收。因此setTimeout执行结束后一秒,我们任然能够执行fn函数。
柯里化
在函数式编程中,利用闭包能够实现很多炫酷的功能,柯里化算是其中一种。关于柯里化,我会在以后详解函数式编程的时候仔细总结。
模块
在我看来,模块是闭包最强大的一个应用场景。如果你是初学者,对于模块的了解可以暂时不用放在心上,因为理解模块需要更多的基础知识。但是如果你已经有了很多JavaScript的使用经验,在彻底了解了闭包之后,不妨借助本文介绍的作用域链与闭包的思路,重新理一理关于模块的知识。这对于我们理解各种各样的设计模式具有莫大的帮助。
(function () { var a = 10; var b = 20; function add(num1, num2) { var num1 = !!num1 ? num1 : a; var num2 = !!num2 ? num2 : b; return num1 + num2; } window.add = add; })(); add(10, 20);
在上面的例子中,我使用函数自执行的方式,创建了一个模块。add是模块对外暴露的一个公共方法。而变量a,b被作为私有变量。在面向对象的开发中,我们常常需要考虑是将变量作为私有变量,还是放在构造函数中的this中,因此理解闭包,以及原型链是一个非常重要的事情。模块十分重要,因此我会在以后的文章专门介绍,这里就暂时不多说啦。
为了验证自己有没有搞懂作用域链与闭包,这里留下一个经典的思考题,常常也会在面试中被问到。
利用闭包,修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log(i); }, i*1000 ); }
点此查看关于此题的详细解读
关于作用域链的与闭包我就总结完了,虽然我自认为我是说得非常清晰了,但是我知道理解闭包并不是一件简单的事情,所以如果你有什么问题,可以在评论中问我。你也可以带着从别的地方没有看懂的例子在评论中留言。大家一起学习进步。
Das obige ist der detaillierte Inhalt vonFront-End Advanced (4): Detaillierte Darstellung der Scope-Kette und des Abschlusses. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!