1. Qu'est-ce que la fermeture ?
L'explication officielle est la suivante : une fermeture est une expression (généralement une fonction) qui a de nombreuses variables et un environnement lié à ces variables, donc ces variables font également partie de l'expression
.
Je pense que peu de gens peuvent comprendre directement cette phrase car sa description est trop académique. En fait, cette phrase en termes simples signifie : toutes les fonctions en JavaScript sont des fermetures. Mais de manière générale, la fermeture générée par les fonctions imbriquées est plus puissante, et c'est ce que l'on appelle la plupart du temps « fermeture ». Regardez le code suivant :
function a() { var i = 0; function b() { alert(++i); } return b; } var c = a(); c();
Ce code a deux caractéristiques :
1. La fonction b est imbriquée dans la fonction a
2. La fonction a renvoie la fonction b.
La relation de référence est telle qu'indiquée sur la figure :
De cette façon, après avoir exécuté var c=a(), la variable c pointe en fait sur la fonction b. Après avoir exécuté à nouveau c(), une fenêtre apparaîtra pour afficher la valeur de i (la première fois est 1). Ce code crée en fait une fermeture. Pourquoi ? Parce que la variable c en dehors de la fonction a fait référence à la fonction b dans la fonction a, c'est-à-dire :
Lorsque la fonction interne b de la fonction a est référencée par une variable extérieure à la fonction a, une fermeture est créée.
Soyons plus minutieux. Ce qu'on appelle la « fermeture » consiste à définir une autre fonction dans le corps du constructeur comme fonction de méthode de l'objet cible, et la fonction de méthode de cet objet fait à son tour référence à la variable temporaire dans le corps de fonction externe. Cela permet de conserver indirectement les valeurs des variables temporaires utilisées par le corps du constructeur d'origine tant que l'objet cible peut toujours conserver ses méthodes pendant sa durée de vie. Bien que l'appel initial du constructeur soit terminé et que le nom de la variable temporaire ait disparu, la valeur de la variable peut toujours être référencée dans la méthode de l'objet cible et la valeur n'est accessible que via cette méthode. Même si le même constructeur est appelé à nouveau, seuls de nouveaux objets et méthodes seront générés, et les nouvelles variables temporaires ne correspondent qu'à de nouvelles valeurs, indépendantes du dernier appel.
2. Quelle est la fonction de la fermeture ?
En bref, la fonction de la fermeture est qu'après l'exécution et le retour de a, la fermeture empêche le mécanisme de récupération de place de Javascript GC de récupérer les ressources occupées par a, car l'exécution de la fonction interne de a b doit dépendre de a. variables dans . Il s’agit d’une description très simple du rôle des fermetures. Elle n’est ni professionnelle ni rigoureuse, mais le sens général est le suivant : Comprendre les fermetures nécessite un processus étape par étape.
Dans l'exemple ci-dessus, en raison de l'existence d'une fermeture, i dans a existera toujours après le retour de la fonction a. De cette façon, chaque fois que c() est exécuté, i sera la valeur de i qui est alertée après l'ajout. 1.
Alors imaginons une autre situation. Si a renvoie autre chose que la fonction b, la situation est complètement différente. Parce qu'après l'exécution de a, b n'est pas renvoyé au monde extérieur de a, mais n'est référencé que par a. À ce stade, a ne sera référencé que par b. Par conséquent, les fonctions a et b se réfèrent l'une à l'autre mais ne sont pas perturbées. par le monde extérieur (référencé par le monde extérieur), les fonctions a et b seront recyclées par GC. (Le mécanisme de récupération de place de Javascript sera présenté en détail plus tard)
3. Le monde microscopique au sein de la fermeture
Si nous voulons avoir une compréhension plus approfondie des fermetures et de la relation entre la fonction a et la fonction imbriquée b, nous devons introduire plusieurs autres concepts : contexte d'exécution de la fonction (contexte d'exécution), objet actif (objet d'appel), portée (portée ) ), chaîne de portée. Prenons comme exemple le processus de la fonction a, de la définition à l'exécution, pour illustrer ces concepts.
À ce stade, les étapes allant de la définition à l'exécution de l'ensemble de la fonction a sont terminées. À ce stade, a renvoie la référence de la fonction b à c, et la chaîne de portée de la fonction b contient une référence à l'objet actif de la fonction a, ce qui signifie que b peut accéder à toutes les variables et fonctions définies dans a. La fonction b est référencée par c et la fonction b dépend de la fonction a, donc la fonction a ne sera pas recyclée par GC après son retour.
Lorsque la fonction b est exécutée, ce sera la même chose que les étapes ci-dessus. Par conséquent, la chaîne de portée de b lors de l’exécution contient 3 objets : l’objet actif de b, l’objet actif de a et l’objet fenêtre, comme le montre la figure suivante :
如图所示,当在函数b中访问一个变量的时候,搜索顺序是:
小结,本段中提到了两个重要的词语:函数的定义与执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定(参看步骤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个假设是正确的,说明函数的作用域确实是在定义这个函数的时候就已经确定了。
四、闭包的应用场景
保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
通过保护变量的安全实现JS私有属性和私有方法(不能被外部访问)
私有属性和方法在Constructor外是无法被访问的
function Constructor(...) { var that = this; var membername = value; function membername(...) {...} }
以上3点是闭包最基本的应用场景,很多经典案例都源于此。
五、Javascript的垃圾回收机制
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。
六、结语
理解了aScript的闭包的解释和运行机制才能写出更为安全和优雅的代码,希望对大家的学习有所帮助。