Regardons quelques définitions des fermetures :
Une fermeture est une fonction qui a accès à une variable dans le cadre d'une autre fonction - - "JS Advanced Programming Third Edition" p178
Les objets de fonction peuvent être liés via des chaînes de portée, et les variables à l'intérieur du corps de la fonction peuvent être enregistrées dans la portée de la fonction. Cette fonctionnalité est appelée « fermeture ». . -- "The Definitive Guide to JS" p183
Les fonctions internes peuvent accéder aux paramètres et variables des fonctions externes dans lesquelles elles sont définies (sauf this
et arguments
). -- "JS Language Essence" p36
Résumons la définition
Vous pouvez accéder aux variables dans le cadre de fonctions externes 函数
Les variables des fonctions externes accessibles par les fonctions internes peuvent être enregistrées dans le cadre de la fonction externe sans être recyclées --- c'est le noyau Nous rencontrerons des fermetures. plus tard. Pour réfléchir, nous devons nous concentrer sur la variable référencée par la fermeture.
pour créer une fermeture simple
var sayName = function(){var name = 'jozo';return function(){ alert(name); } };var say = sayName(); say();
pour interpréter les deux affirmations suivantes :
var say = sayName()
: renvoie une fonction interne anonyme stockée dans la variable say et fait référence au nom de variable de la fonction externe. En raison du mécanisme de garbage collection, le nom de la variable n'est pas détruit après l'exécution de la fonction sayName.
say()
: Exécutez la fonction interne renvoyée, vous pouvez toujours accéder au nom de la variable, sortie 'jozo'.
Comprendre les chaînes de portée est également utile pour comprendre les fermetures.
Vous devez être familier avec la façon dont les variables sont recherchées dans la portée. En fait, il s'agit d'une recherche dans la chaîne de portée.
Lorsque la fonction est appelée :
Créez d'abord un contexte d'exécution et la chaîne de portée correspondante
Ajouter les valeurs des arguments et autres paramètres nommés à l'objet d'activation de la fonction
Chaîne de portée : la priorité de l'objet d'activation de la fonction actuelle La plus élevée, suivie des objets actifs des fonctions externes et les objets actifs des fonctions externes des fonctions externes diminuent dans l'ordre jusqu'à la fin de la chaîne de portée - la portée globale. La priorité est l'ordre dans lequel les variables sont recherchées ;
Examinons d'abord une chaîne de portées commune :
function sayName(name){return name; }var say = sayName('jozo');
Ce code contient deux portées : a.
Global. scope; b.
La portée de la fonction sayName, c'est-à-dire qu'il n'y a que deux objets variables. Lorsqu'il est exécuté dans l'environnement d'exécution correspondant, l'objet variable deviendra un objet actif et sera poussé dans la chaîne de portée de l'environnement d'exécution. est celui qui a la plus haute priorité. Regardez l'image et parlez :
Cette image est également dans le livre de programmation avancée JS, et je l'ai redessinée.
Lors de la création de la fonction sayName(), une chaîne de portée qui contient pré-contenant l'objet variable est créée, c'est-à-dire la chaîne de portée avec l'index 1 dans la figure, et est enregistrée dans le [[Scope] interne ] , lorsque la fonction sayName() est appelée, un environnement d'exécution est créé, puis la chaîne de domaines active est construite en copiant l'objet dans l'attribut [[Scope]] de la fonction. Après cela, il y a un autre objet actif. (index 0 dans la figure) ) est créé et placé au premier plan de la chaîne de portée de l'environnement d'exécution.
D'une manière générale, lorsque la fonction est exécutée, l'objet actif local sera détruit, et seule la portée globale sera enregistrée en mémoire. Cependant, la situation des fermetures est différente :
Jetons un coup d'œil à la chaîne de portée des fermetures :
function sayName(name){return function(){return name; } }var say = sayName('jozo');
Cette instance de fermeture est meilleure que la précédente L'exemple ajoute la portée d'une fonction anonyme :
Une fois la fonction anonyme renvoyée par la fonction sayName(), sa chaîne de portée est initialisée à l'objet actif et l'objet variable globale contenant la fonction sayName(). De cette façon, la fonction anonyme peut accéder à toutes les variables et paramètres définis dans sayName(). Plus important encore, une fois la fonction sayName() exécutée, son objet actif ne sera pas détruit en raison de la chaîne de portée de cette fonction anonyme. L'objet est toujours référencé. En d'autres termes, après l'exécution de la fonction sayName(), la chaîne de portée de son environnement d'exécution sera détruite, mais son objet actif restera en mémoire jusqu'à ce que la fonction anonyme soit détruite. C'est également le problème de fuite de mémoire qui sera abordé plus tard.
Je n'écris pas beaucoup sur les problèmes de chaîne de portée, et écrire des choses dans des livres est aussi très fatiguant o(╯□╰)o
Exemple 1 : Implémenter l'accumulation
// 方式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更加优雅,也减少全局变量,将变量私有化
Exemple 2 : Ajouter des événements de clic à chaque li
var oli = document.getElementsByTagName('li'); var i; for(i = 0;i < 5;i++){ oli[i].onclick = function(){ alert(i); } } console.log(i); // 5 //执行匿名函数 (function(){ alert(i); //5 }());
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; // 将闭包引用的外部函数中活动对象清除 }
优点:
可以让一个变量常驻内存 (如果用的多了就成了缺点
避免全局变量的污染
私有化变量
缺点
Parce que la fermeture portera la portée de la fonction qui la contient, elle occupera plus de mémoire que les autres fonctions
Provoquer des fuites de mémoire
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!