Ich habe viele Informationen zu JavaScript-Schließungen im Internet überprüft und die meisten davon waren sehr akademisch und professionell. Für Anfänger sind selbst Textbeschreibungen schwer zu verstehen, ganz zu schweigen vom Verständnis von Abschlüssen. Der Zweck des Schreibens dieses Artikels besteht darin, die beliebtesten Wörter zu verwenden, um das wahre Gesicht von JavaScript-Abschlüssen zu enthüllen.
1. Was ist Schließung?
Die „offizielle“ Erklärung lautet: Ein Abschluss ist ein Ausdruck (normalerweise eine Funktion), der viele Variablen und eine an diese Variablen gebundene Umgebung hat, sodass diese Variablen auch Teil des Ausdrucks sind. Ich glaube, dass nur wenige Menschen diesen Satz direkt verstehen können, weil seine Beschreibung zu akademisch ist. Tatsächlich bedeutet dieser Satz für Laien: Alle Funktionen in JavaScript sind Abschlüsse. Aber im Allgemeinen ist der durch verschachtelte Funktionen erzeugte Abschluss leistungsfähiger, was wir meistens als „Abschluss“ bezeichnen. Schauen Sie sich den folgenden Code an:
function a() { var i = 0; function b() { alert(++i); } return b; } var c = a(); c();
Dieser Code hat zwei Eigenschaften:
Funktion b ist in Funktion a verschachtelt;
Funktion a gibt Funktion b zurück.
Auf diese Weise zeigt die Variable c nach der Ausführung von var c=a() tatsächlich auf die Funktion b. Nach der Ausführung von c() wird ein Fenster angezeigt, in dem der Wert von angezeigt wird i. (Das erste Mal ist 1). Dieser Code erstellt tatsächlich einen Abschluss. Weil die Variable c außerhalb der Funktion a auf die Funktion b innerhalb der Funktion a verweist, das heißt: Wenn die interne Funktion b der Funktion a von einer Variablen außerhalb der Funktion a referenziert wird, entsteht ein sogenannter „Abschluss“.
Lassen Sie uns gründlicher vorgehen. Der sogenannte „Abschluss“ besteht darin, eine andere Funktion im Konstruktorkörper als Methodenfunktion des Zielobjekts zu definieren, und die Methodenfunktion dieses Objekts verweist wiederum auf die temporäre Variable im äußeren Funktionskörper. Dadurch können die vom ursprünglichen Konstruktorkörper verwendeten temporären Variablenwerte indirekt beibehalten werden, solange das Zielobjekt seine Methoden während seiner Lebensdauer immer beibehalten kann. Obwohl der anfängliche Konstruktoraufruf beendet wurde und der Name der temporären Variablen verschwunden ist, kann der Wert der Variablen immer innerhalb der Methode des Zielobjekts referenziert werden, und auf den Wert kann nur über diese Methode zugegriffen werden. Selbst wenn derselbe Konstruktor erneut aufgerufen wird, werden nur neue Objekte und Methoden generiert und die neuen temporären Variablen entsprechen nur neuen Werten, die vom letzten Aufruf unabhängig sind.
Um ein tieferes Verständnis von Schließungen zu erlangen, wollen wir weiterhin die Funktionen und Auswirkungen von Schließungen erforschen.
2. Welche Funktionen und Auswirkungen hat der Verschluss?
Kurz gesagt besteht die Funktion des Abschlusses darin, dass nach der Ausführung und Rückgabe von a der Garbage-Collection-Mechanismus GC von Javascript daran gehindert wird, die von a aufgrund der Ausführung der internen Funktion b belegten Ressourcen zurückzugewinnen Verlassen Sie sich auf Variablen in a. Dies ist eine sehr einfache Beschreibung der Rolle von Abschlüssen. Sie ist weder professionell noch streng, aber Sie können sie durchaus verstehen. Das Verständnis von Schließungen erfordert einen schrittweisen Prozess.
Im obigen Beispiel existiert i in a immer, nachdem Funktion a zurückgegeben wird. Auf diese Weise ist i jedes Mal, wenn c() ausgeführt wird, der Wert von i nach dem Hinzufügen von 1 alarmiert. .
Dann stellen wir uns eine andere Situation vor. Wenn a etwas anderes als Funktion b zurückgibt, ist die Situation völlig anders. Denn nach der Ausführung von a wird b nicht an die Außenwelt von a zurückgegeben, sondern nur von a referenziert. Daher verweisen die Funktionen a und b aufeinander, werden aber nicht gestört von der Außenwelt (von der Außenwelt referenziert), werden die Funktionen a und b von GC recycelt.
3. Die Mikrowelt der Abschlüsse
Wenn wir ein tieferes Verständnis der Abschlüsse und der Beziehung zwischen Funktion a und verschachtelter Funktion b haben wollen, müssen wir mehrere andere Konzepte einführen: die Ausführung Umgebung der Funktion (Ausführungskontext), aktives Objekt (Aufrufobjekt), Geltungsbereich (Scope), Geltungsbereichskette (Scope Chain). Nehmen Sie als Beispiel den Prozess der Funktion a von der Definition bis zur Ausführung, um diese Konzepte zu veranschaulichen.
Beim Definieren der Funktion a legt der js-Interpreter die Bereichskette der Funktion a auf die „Umgebung“ fest, in der sich a beim Definieren von a befindet. Wenn a eine globale Funktion ist, befindet sich in der Bereichskette nur ein Fensterobjekt Es.
Beim Ausführen der Funktion a gelangt a in den entsprechenden Ausführungskontext.
Beim Erstellen der Ausführungsumgebung wird zunächst ein Bereichsattribut zu a hinzugefügt, dh dem Bereich von a, und sein Wert ist die Bereichskette in Schritt 1. Das heißt, die Bereichskette von a.scope=a.
Die Ausführungsumgebung erstellt dann ein Aufrufobjekt. Ein aktives Objekt ist ebenfalls ein Objekt, das über Eigenschaften verfügt, aber keinen Prototyp hat und auf das nicht direkt über JavaScript-Code zugegriffen werden kann. Nachdem Sie das aktive Objekt erstellt haben, fügen Sie das aktive Objekt an der Spitze der Bereichskette von a hinzu. Zu diesem Zeitpunkt enthält die Bereichskette von a zwei Objekte: das aktive Objekt von a und das Fensterobjekt.
Der nächste Schritt besteht darin, dem aktiven Objekt ein Argumentattribut hinzuzufügen, das die beim Aufruf von Funktion a übergebenen Parameter enthält.
最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。
到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。
当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象
当在函数b中访问一个变量的时候,搜索顺序是:
先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依次查找,直到找到为止。
如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。
如果整个作用域链上都无法找到,则返回undefined。
小结,本段中提到了两个重要的词语:函数的定义与执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定(参看步骤1和3)。用一段代码来说明这个问题:
function f(x) { var g = function () { return x; } return g; } var h = f(1); alert(h());
这段代码中变量h指向了f中的那个匿名函数(由g返回)。
假设函数h的作用域是在执行alert(h())确定的,那么此时h的作用域链是:h的活动对象->alert的活动对象->window对象。
假设函数h的作用域是在定义时确定的,就是说h指向的那个匿名函数在定义的时候就已经确定了作用域。那么在执行的时候,h的作用域链为:h的活动对象->f的活动对象->window对象。
如果第一种假设成立,那输出值就是undefined;如果第二种假设成立,输出值则为1。运行结果证明了第2个假设是正确的,说明函数的作用域确实是在定义这个函数的时候就已经确定了。
4. 闭包的应用场景
保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
function Constructor(...) { var that = this; var membername = value; function membername(...) {...} }
以上3点是闭包最基本的应用场景,很多经典案例都源于此。
5. JavaScript的垃圾回收机制
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。
6. 结语
理解JavaScript的闭包是迈向高级JS程序员的必经之路,理解了其解释和运行机制才能写出更为安全和优雅的代码。