Sehen wir uns einige Definitionen von Abschlüssen an:
Ein Abschluss ist eine Funktion, die Zugriff auf eine Variable im Gültigkeitsbereich einer anderen Funktion hat – - „JS Advanced Programming Third Edition“ p178
Funktionsobjekte können über Bereichsketten verknüpft werden, und Variablen innerhalb des Funktionskörpers können im Funktionsbereich gespeichert werden. Diese Funktion wird als „Abschluss“ bezeichnet. . -- „The Definitive Guide to JS“ p183
Interne Funktionen können auf die Parameter und Variablen der externen Funktionen zugreifen, in denen sie definiert sind (außer this
und arguments
). -- „JS Language Essence“ S. 36
Lassen Sie uns die Definition zusammenfassen
Sie können auf Variablen im Bereich von zugreifen Externe Funktionen 函数
Variablen externer Funktionen, auf die interne Funktionen zugreifen, können im Umfang der externen Funktion gespeichert werden, ohne recycelt zu werden – das ist der Kern. Wir werden auf Schließungen stoßen Später müssen wir uns auf die Variable konzentrieren, auf die der Abschluss verweist.
um einen einfachen Abschluss zu erstellen
var sayName = function(){var name = 'jozo';return function(){ alert(name); } };var say = sayName(); say();
um die folgenden zwei Aussagen zu interpretieren:
var say = sayName()
: Gibt eine anonyme interne Funktion zurück, die in der Variablen say gespeichert ist, und verweist auf den Variablennamen der externen Funktion. Aufgrund des Garbage-Collection-Mechanismus wird der Variablenname nach der Ausführung der Funktion sayName nicht zerstört.
say()
: Führen Sie die zurückgegebene interne Funktion aus. Sie können weiterhin auf den Variablennamen zugreifen und „jozo“ ausgeben.
Das Verständnis von Scope-Ketten ist auch hilfreich für das Verständnis von Abschlüssen.
Sie sollten mit der Art und Weise vertraut sein, wie Variablen im Bereich durchsucht werden. Tatsächlich handelt es sich dabei um eine Suche in der Bereichskette.
Wenn die Funktion aufgerufen wird:
Erstellen Sie zunächst einen Ausführungskontext und die entsprechende Bereichskette;
Fügen Sie die Werte von Argumenten und anderen benannten Parametern zum Aktivierungsobjekt der Funktion hinzuSchauen wir uns zunächst eine allgemeine Bereichskette an:
Dieser Code enthält zwei Bereiche:
Global Bereich;function sayName(name){return name; }var say = sayName('jozo');
a.
b.
Dieses Bild befindet sich auch im JS-Programmierbuch für Fortgeschrittene, und ich habe es noch einmal gezeichnet.
Beim Erstellen der Funktion sayName() wird eine Bereichskette erstellt, die das variable Objekt vorab enthält, d. h. die Bereichskette mit Index 1 in der Abbildung, und im internen [[Scope] gespeichert. ]-Attribut: Wenn die Funktion sayName() aufgerufen wird, wird eine Ausführungsumgebung erstellt und dann die aktive Domänenkette erstellt, indem das Objekt in das Attribut [[Scope]] der Funktion kopiert wird. Danach gibt es ein weiteres aktives Objekt (Index 0 in der Abbildung)) wird erstellt und an den Anfang der Bereichskette der Ausführungsumgebung verschoben. Im Allgemeinen wird beim Ausführen der Funktion das lokal aktive Objekt zerstört und nur der globale Bereich im Speicher gespeichert. Bei Schließungen ist die Situation jedoch anders:Werfen wir einen Blick auf die Scope-Kette der Schließungen:
Diese Schließungsinstanz ist besser als die vorherige Das Beispiel fügt den Bereich einer anonymen Funktion hinzu:
function sayName(name){return function(){return name; } }var say = sayName('jozo');
Nachdem die anonyme Funktion von der Funktion sayName() zurückgegeben wurde, wird ihre Bereichskette initialisiert das aktive Objekt und das globale Variablenobjekt, das die Funktion sayName() enthält. Auf diese Weise kann die anonyme Funktion auf alle in sayName() definierten Variablen und Parameter zugreifen. Noch wichtiger ist, dass ihr aktives Objekt aufgrund der Gültigkeitskette der anonymen Funktion nicht zerstört wird Das Objekt wird immer noch referenziert. Mit anderen Worten: Nachdem die Funktion sayName() ausgeführt wurde, wird die Bereichskette ihrer Ausführungsumgebung zerstört, ihr aktives Objekt bleibt jedoch im Speicher, bis die anonyme Funktion zerstört wird. Dies ist auch das Problem des Speicherverlusts, auf das später noch eingegangen wird.
Ich schreibe nicht so viel über Scope-Chain-Probleme und das Schreiben von Dingen in Büchern ist auch sehr ermüdend o(╯□╰)o3 Beispiele für AbschlüsseBeispiel 2: Klickereignisse zu jedem Li hinzufügen
// 方式1var a = 0;var add = function(){ a++; console.log(a) }add();add();//方式2 :闭包var add = (function(){ var a = 0; return function(){ a++; console.log(a); } })(); console.log(a); //undefinedadd();add(); 相比之下方式2更加优雅,也减少全局变量,将变量私有化
Das Obige ist ein Klassiker Beispielsweise wissen wir alle, dass als Ausführungsergebnis 5 angezeigt wird, und wir wissen auch, dass der Verschluss zur Lösung dieses Problems verwendet werden kann, aber am Anfang konnte ich immer noch nicht verstehen, warum jedes Mal 5 angezeigt wird und warum der Verschluss das Problem lösen kann dieses Problem. Nach einigem Suchen habe ich es endlich herausgefunden:
a. 先来分析没用闭包前的情况:for循环中,我们给每个li点击事件绑定了一个匿名函数,匿名函数中返回了变量i的值,当循环结束后,变量i的值变为5,此时我们再去点击每个li,也就是执行相应的匿名函数(看上面的代码),这是变量i已经是5了,所以每个点击弹出5. 因为这里返回的每个匿名函数都是引用了同一个变量i,如果我们新建一个变量保存循环执行时当前的i的值,然后再让匿名函数应用这个变量,最后再返回这个匿名函数,这样就可以达到我们的目的了,这就是运用闭包来实现的!
b. 再来分析下运用闭包时的情况:
var oli = document.getElementsByTagName('li'); var i; for(i = 0;i < 5;i++){ oli[i].onclick = (function(num){ var a = num; // 为了说明问题 return function(){ alert(a); } })(i) } console.log(i); // 5
这里for循环执行时,给点击事件绑定的匿名函数传递i后立即执行返回一个内部的匿名函数,因为参数是按值传递的,所以此时形参num保存的就是当前i的值,然后赋值给局部变量 a,然后这个内部的匿名函数一直保存着a的引用,也就是一直保存着当前i的值。 所以循环执行完毕后点击每个li,返回的匿名函数执行弹出各自保存的 a 的引用的值。
我们来看看闭包的用途。事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。
1. 匿名自执行函数
我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,比如UI的初始化,那么我们可以使用闭包:
//将全部li字体变为红色 (function(){ var els = document.getElementsByTagName('li');for(var i = 0,lng = els.length;i < lng;i++){ els[i].style.color = 'red'; } })();
我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,
因此els,i,lng这些局部变量在执行完后很快就会被释放,节省内存!
关键是这种机制不会污染全局对象。
2. 实现封装/模块化代码
var person= function(){ //变量作用域为函数内部,外部无法访问 var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }();console.log(person.name);//直接访问,结果为undefined console.log(person.getName()); //default person.setName("jozo"); console.log(person.getName()); //jozo
3. 实现面向对象中的对象
这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,
我们可以模拟出这样的机制。还是以上边的例子来讲:
function Person(){ var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }; var person1= Person(); print(person1.getName()); john.setName("person1"); print(person1.getName()); // person1 var person2= Person(); print(person2.getName()); jack.setName("erson2"); print(erson2.getName()); //person2
Person的两个实例person1 和 person2 互不干扰!因为这两个实例对name这个成员的访问是独立的 。
垃圾回收机制
说到内存管理,自然离不开JS中的垃圾回收机制,有两种策略来实现垃圾回收:标记清除 和 引用计数;
标记清除:
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后,它会去掉环境中的变量的标记和被环境中的变量引用的变量的标记,此后,如果变量再被标记则表示此变量准备被删除。 2008年为止,IE,Firefox,opera,chrome,Safari的javascript都用使用了该方式;
引用计数:
跟踪记录每个值被引用的次数,当声明一个变量并将一个引用类型的值赋给该变量时,这个值的引用次数就是1,如果这个值再被赋值给另一个变量,则引用次数加1。相反,如果一个变量脱离了该值的引用,则该值引用次数减1,当次数为0时,就会等待垃圾收集器的回收。
这个方式存在一个比较大的问题就是循环引用,就是说A对象包含一个指向B的指针,对象B也包含一个指向A的引用。 这就可能造成大量内存得不到回收(内存泄露),因为它们的引用次数永远不可能是 0 。早期的IE版本里(ie4-ie6)采用是计数的垃圾回收机制,闭包导致内存泄露的一个原因就是这个算法的一个缺陷。
我们知道,IE中有一部分对象并不是原生额javascript对象,例如,BOM和DOM中的对象就是以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数。因此,虽然IE的javascript引擎采用的是标记清除策略,但是访问COM对象依然是基于引用计数的,因此只要在IE中设计COM对象就会存在循环引用的问题!
举个栗子:
window.onload = function(){var el = document.getElementById("id"); el.onclick = function(){ alert(el.id); } }
这段代码为什么会造成内存泄露?
el.onclick= function () { alert(el.id); };
执行这段代码的时候,将匿名函数对象赋值给el的onclick属性;然后匿名函数内部又引用了el对象,存在循环引用,所以不能被回收;
解决方法:
window.onload = function(){var el = document.getElementById("id");var id = el.id; //解除循环引用 el.onclick = function(){ alert(id); } el = null; // 将闭包引用的外部函数中活动对象清除 }
优点:
可以让一个变量常驻内存 (如果用的多了就成了缺点
避免全局变量的污染
私有化变量
缺点
Da der Abschluss den Umfang der Funktion trägt, die ihn enthält, belegt er mehr Speicher als andere Funktionen
Verursacht Speicherlecks
Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in Verschlüsse in js. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!