Maison  >  Article  >  interface Web  >  Les fermetures JavaScript sont-elles des portées ?

Les fermetures JavaScript sont-elles des portées ?

青灯夜游
青灯夜游original
2021-09-07 16:58:121742parcourir

En JavaScript, une fermeture n'est pas une portée, mais un objet actif de contexte de fonction qui peut persister. C'est un objet qui contient des références à la fois à l'objet fonction et à l'objet portée. Les fermetures sont principalement utilisées pour obtenir des variables ou des valeurs sur la chaîne de portée ou la chaîne de prototypes.

Les fermetures JavaScript sont-elles des portées ?

L'environnement d'exploitation de ce tutoriel : système Windows 7, JavaScript version 1.8.5, ordinateur Dell G3.

Nous savons que l'ordre de recherche des identifiants dans la chaîne de portée est de rechercher un niveau au-dessus de la portée actuelle. Par conséquent, grâce à une chaîne de portées, les variables à l'intérieur d'une fonction JavaScript peuvent lire des variables en dehors de la fonction, mais à l'inverse, les variables en dehors de la fonction ne peuvent généralement pas lire les variables à l'intérieur de la fonction. Dans les applications pratiques, il est parfois nécessaire d'accéder aux variables locales d'une fonction en dehors de la fonction. Dans ce cas, la méthode la plus courante consiste à utiliser des fermetures.

La fermeture est l'une des fonctionnalités importantes de JavaScript et joue un rôle important dans la programmation fonctionnelle. Cette section présente la structure et l'utilisation de base de la fermeture.

Alorsc'est quoi la fermeture ?

Une fermeture est un objet actif de contexte de fonction qui peut persister. C'est un objet qui contient des références à la fois aux objets de fonction et aux objets de portée. Les fermetures sont principalement utilisées pour obtenir des variables ou des valeurs sur la chaîne de portée ou la chaîne de prototypes. La manière la plus courante de créer une fermeture consiste à déclarer une fonction interne (également appelée fonction imbriquée) dans une fonction et à renvoyer la fonction interne.

À ce stade, vous pouvez obtenir la fonction interne en appelant la fonction en dehors de la fonction, puis appeler la fonction interne pour accéder aux variables locales de la fonction. La fonction interne à ce moment est une fermeture. Bien que selon le concept de fermeture, toutes les fonctions JavaScript qui accèdent à des variables externes soient des fermetures, la plupart du temps, ce que nous appelons fermetures fait en réalité référence à des fermetures de fonctions internes.

Les fermetures peuvent encapsuler certaines données en tant que propriétés privées pour garantir un accès sécurisé à ces variables. Cette fonctionnalité apporte de grands avantages aux applications. Il convient de noter que si les fermetures sont mal utilisées, elles peuvent également causer des problèmes inattendus. Ci-dessous, nous utiliserons plusieurs exemples pour démontrer la création, l'utilisation, les problèmes possibles et les solutions des fermetures.

Principe de formation

Lorsque la fonction est appelée, un objet d'activité contextuel temporaire sera généré. Il s'agit de l'objet de niveau supérieur de la portée de la fonction. Toutes les méthodes privées, variables, paramètres, fonctions privées, etc. dans la portée existeront en tant qu'attributs de l'objet actif du contexte. Après l'appel de la fonction

, par défaut, l'objet actif du contexte sera immédiatement libéré pour éviter d'occuper les ressources système. Cependant, si les variables privées, les paramètres, les fonctions privées, etc. au sein de la fonction sont référencés par le monde extérieur, l'objet actif du contexte continuera d'exister temporairement jusqu'à ce que toutes les références externes soient annulées.

Cependant, la portée de la fonction est fermée et n'est pas accessible au monde extérieur. Alors, dans quelles circonstances le monde extérieur peut-il accéder aux membres privés d’une fonction ?

Selon la chaîne de portée, la fonction interne peut accéder aux membres privés de la fonction externe. Si la fonction interne fait référence aux membres privés de la fonction externe et que la fonction interne est transmise au monde extérieur ou est ouverte au monde extérieur, alors un corps de fermeture est formé. Cette fonction externe est un corps de fermeture.Après son appel, l'objet actif ne sera pas annulé temporairement et ses propriétés continueront d'exister. Les membres privés de la fonction externe peuvent être lus et écrits en continu via la fonction interne.

Structure de fermeture

Un corps de fermeture typique est fonction d'une structure imbriquée. La fonction interne fait référence aux membres privés de la fonction externe, et en même temps la fonction interne est référencée par le monde extérieur. Lorsque la fonction externe est appelée, une fermeture est formée. Cette fonction est également appelée fonction de fermeture.

Ce qui suit est une structure de fermeture typique.

function f(x) {  //外部函数
    return function (y) {  //内部函数,通过返回内部函数,实现外部引用
        return x + y;  //访问外部函数的参数
    };
}
var c = f(5);  //调用外部函数,获取引用内部函数
console.log(c(6));  //调用内部函数,原外部函数的参数继续存在

Le processus d'analyse est brièvement décrit comme suit :

  • Pendant la période de pré-compilation du script JavaScript, la fonction f déclarée et la variable c sont d'abord pré-analysées lexicalement.

  • Pendant l'exécution de JavaScript, appelez la fonction f et transmettez la valeur 5.

  • Lors de l'analyse de la fonction f, l'environnement d'exécution (portée de la fonction) et l'objet actif seront créés, et les paramètres, les variables privées et les fonctions internes seront mappés aux propriétés de l'objet actif.

  • La valeur du paramètre x est 5, qui correspond à l'attribut x de l'objet actif.

  • La fonction interne fait référence au paramètre x via la chaîne de portée, mais n'a pas encore été exécutée.

  • Une fois la fonction externe appelée, la fonction interne revient, provoquant le référencement de la fonction interne par la variable externe c.

  • L'analyseur JavaScript détecte que les propriétés de l'objet actif de la fonction externe sont référencées par le monde extérieur et ne peut pas désenregistrer l'objet actif, il continue donc à maintenir l'existence de l'objet dans la mémoire.

  • Lorsque c est appelé, c'est-à-dire lorsque la fonction interne est appelée, vous pouvez voir que la valeur stockée dans le paramètre x de la fonction externe continue d'exister. De cette manière, les opérations ultérieures peuvent être implémentées et x+y=5=6=11 est renvoyé.

La forme structurelle suivante peut également former une fermeture : faire référence à la fonction interne via la variable globale, afin que la fonction interne puisse être ouverte sur le monde extérieur.

var c;  //声明全局变量
function f(x) {  //外部函数
    c = function (y) {  //内部函数,通过向全局变量开放实现外部引用
        return x + y;  //访问外部函数的参数
    };
}
f(5);  //调用外部函数
console.log(c(6));  //使用全局变量c调用内部函数,返回11

Variantes de fermeture

En plus des fonctions imbriquées, les fermetures sont également facilement formées si des références externes sont faites à des tableaux privés ou à des objets à l'intérieur de la fonction.

var add;  //全局变量
function f() {  //外部函数
    var a = [1,2,3];  //私有变量,引用型数组
    add = function (x) {  //测试函数,对外开放
        a[0] = x * x;  //修改私有数组的元素值
    }
    return a;  //返回私有数组的引用
}
var c = f();
console.log(c[0]);  //读取闭包内数组,返回1
add(5);  //测试修改数组
console.log(c[0]);  //读取闭包内数组,返回25
add(10);  //测试修改数组
console.log(c[0]);  //读取闭包内数组,返回100

与函数相同,对象和数组也是引用型数据。调用函数 f,返回私有数组 a 的引用,即传值给局部变量 c,而 a 是函数 f 的私有变量,当被调用后,活动对象继续存在,这样就形成了闭包。

这种特殊形式的闭包没有实际应用价值,因为其功能单一,只能作为一个静态的、单向的闭包。而闭包函数可以设计各种复杂的运算表达式,它是函数式变成的基础。

反之,如果返回的是一个简单的值,就无法形成闭包,值传递是直接复制。外部变量 c 得到的仅是一个值,而不是对函数内部变量的引用。这样当函数调用后,将直接注销对象。

function f(x) {  //外部函数
    var a = 1;  //私有变量
    return a;
}
var c = f(5);
console.log(c);  //仅是一个值,返回1

使用闭包

下面结合示例介绍闭包的简单使用,以加深对闭包的理解。

示例1

使用闭包实现优雅的打包,定义存储器。

var f = function () {  //外部函数
    var a = [];  //私有数组初始化
    return function (x) {  //返回内部函数
        a.push(x);  //添加元素
        return a;  //返回私有数组
    };
} ()  //直接调用函数,生成执行环境
var a = f(1);  //添加值
console.log(a);  //返回1
var b = f(2);  //添加值
console.log(b);  //返回1,2

在上面示例中,通过外部函数设计一个闭包,定义一个永久的存储器。当调用外部函数生成执行环境之后,就可以利用返回的匿名函数不断地的向闭包体内的数组 a 传入新值,传入的值会持续存在。

示例2

在网页中事件处理函数很容易形成闭包。

<script>
function f() {
    var a = 1;
    b = function () {
        console.log("a =" + a);
    }
    c = function () {
        a ++;
    }
    d = function () {
        a --;
    }
}
</script>
<button onclick="f()">生成闭包</button>
<button onclick="b()">查看 a 的值</button>
<button onclick="c()">递增</button>
<button onclick="d()">递减</button>

在浏览器中浏览时,首先点击“生成闭包”按钮,生成一个闭包;点击“查看 a 的值”按钮,可以随时查看闭包内私有变量 a 的值;点击“递增”“递减”按钮时,可以动态修改闭包内变量 a 的值,效果如图所示。

Les fermetures JavaScript sont-elles des portées ?

闭包的局限性

闭包的价值是方便在表达式运算过程中存储数据。但是,它的缺点也不容忽视。

  • 由于函数调用后,无法注销调用对象,会占用系统资源,在脚本中大量使用闭包,容易导致内存泄漏。解决方法:慎用闭包,不要滥用。

  • 由于闭包的作用,其保存的值是动态,如果处理不当容易出现异常或错误。

示例

设计一个简单的选项卡效果。HTML 结构如下:

<div class="tab_wrap">
    <ul class="tab" id="tab">
        <li id="tab_1" class="hover">Tab1</li>
        <li id="tab_2" class="normal">Tab2</li>
        <li id="tab_3" class="normal">Tab3</li>
    </ul>
    <div class="content" id="content">
        <div id="content_1" class="show"><img  scr="image/1.jpg"    style="max-width:90%" / alt="Les fermetures JavaScript sont-elles des portées ?" ></div>
        <div id="content_2" class="show"><img  scr="image/2.jpg"    style="max-width:90%" / alt="Les fermetures JavaScript sont-elles des portées ?" ></div>
        <div id="content_3" class="show"><img  scr="image/3.jpg"    style="max-width:90%" / alt="Les fermetures JavaScript sont-elles des portées ?" ></div>
    </div>
</div>

下面请看 JavaScript 脚本。

window.onload = function () {
    var tab = document.getElementById("tab").getElementsByTagName("li"),
        content = document.getElementById("content").getElementByTagName("div");
    for (var i = 0; i < tab.length;i ++) {
        tab[i].addEventListener("mouseover"), function () {
            for (var n = 0; n < tab.length; n ++) {
                tab[n].className = "normal";
                content[n].className = "none";
            }
            tab[i].className = "hover";
            content[i].className = "show";
        });
    }
}

在 load 事件处理函数中,使用 for 语句为每个 li 属性元素绑定 mouseover 事件;在 mouseover 事件处理函数中重置所有选项卡 li 的类样式,然后设置当前 li 选项卡高亮显示,同时显示对应的内容容器。

但是在浏览器中预览时,会发现浏览器抛出异常。

SCRIPT5007:无法设置未定义或 null 引用的属性"className"

在 mouseover 事件处理函数中跟踪变量 i 的值,i 的值都变为了 3,tab[3] 自然是一个 null,所以也不能够读取 className 属性。

【原因分析】

上面 JavaScript 代码是一个典型的嵌套函数结构。外部函数为 load 事件处理函数,内部函数为 mouseover 事件处理函数,变量 i 为外部函数的私有变量。

通过事件绑定,mouseover 事件处理函数被外界引用(li 元素),这样就形成了一个闭包体。虽然在 for 语句中为每个选项卡 li 分别绑定事件处理函数,但是这个操作是动态的,因此 tab[i] 中 i 的值也是动态的,所以就出现了上述异常。

【解决方法】

解决闭包的缺陷,最简单的方法是阻断内部函数对外部函数的变量引用,这样就形成了闭包体。针对本示例,我们可以在内部函数(mouseover 事件处理函数)外边增加一层防火墙,不让其直接引用外部变量。

window.load = function () {
    var tab = document.getElementById("tab").getElementsByTagName("li"),
        content = document.getElementById("content").getElementsByTagName("div");
    for (var i = 0; i < tab.length; i ++ ) {
        (function (j) {
            tab[j].addEventListener("number", function () {
                for (var n = 0; n < tab.length; n ++) {
                    tab[n].className = "normal";
                    content[n].className = "none";
                }
                tab[j].className = "hover";
                conteng[j].className = "show";
            });
        }) (i);
    }
}

在 for 语句中,直接调用匿名函数,把外部函数的 i 变量传给调用函数,在调用函数中接收这个值,而不是引用外部变量 i,规避了闭包体带来的困惑。

【推荐学习:javascript高级教程

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn