Maison  >  Article  >  interface Web  >  Découvrons ensemble les fermetures

Découvrons ensemble les fermetures

coldplay.xixi
coldplay.xixiavant
2020-09-08 13:25:052597parcourir

Découvrons ensemble les fermetures

Recommandations d'apprentissage associées : Tutoriel vidéo javascript

Avant-propos

La fermeture est toujours un obstacle que les développeurs front-end ne peuvent pas contourner. Que cela vous plaise ou non, vous le rencontrerez au travail et lors des entretiens. La compréhension que chacun a de la clôture est différente. Ici, je vais parler de ma propre compréhension de la clôture. (S'il y a un écart avec votre compréhension, veuillez vous référer à vous-même)

Comment définir la fermeture

Avant de donner la définition, autant jeter un œil à la façon dont les autres définissent la fermeture :

Les objets de fonction peuvent être liés les uns aux autres 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 » dans la littérature informatique - Guide d'autorité JavaScript (. 6e édition)

Une fermeture est une fonction qui a accès à une variable dans la portée d'une autre fonction. Une manière courante de créer une fermeture consiste à créer une fonction à l’intérieur d’une autre fonction. -- JavaScript Advanced Programming (Troisième édition)

Une fermeture est créée lorsqu'une fonction peut se souvenir et accéder à la portée lexicale dans laquelle elle se trouve, même si la fonction est dans la portée lexicale actuelle. Exécution en dehors du domaine. -- JavaScript que vous ne connaissez pas (Volume 1)

Bien que les descriptions dans les paragraphes ci-dessus soient différentes, vous pouvez quand même trouver quelques points communs après une lecture attentive. Le plus important est la connexion entre les différents périmètres. Bien sûr, vous pouvez citer directement les définitions ci-dessus (après tout, les définitions ci-dessus font relativement autorité). Ici, l'auteur préfère la définition du dernier paragraphe, et recommande également fortement le livre "JavaScript You Don't Know (Volume 1). )", qui vaut la peine d'être lu attentivement et à plusieurs reprises.

Quels points de connaissance sont impliqués dans la fermeture

Il ne suffit pas de donner une définition, vous devez également explorer quels points de connaissance sont impliqués en interne. Voici les points de connaissances que l'auteur juge utiles.

Portée et chaîne de portée

Eh bien, en fait, l'auteur sait que vous y avez tous pensé (non, personne n'y a pensé). Maintenant que tout le monde connaît la portée . Ici, je vais le décrire brièvement et passer en revue le processus.

Portée : un ensemble de règles pour rechercher des variables par nom. Divisé en trois types : portée globale ; portée de la fonction ;

Ce qu'il faut noter, c'est la portée du bloc, une nouvelle spécification dans ES6. Les variables définies à l'aide de {} entre accolades let,const seront liées à la portée et ne seront pas accessibles en dehors des accolades. Remarque : Entre le début des accolades et avant la déclaration de la variable let, il y a 暂时性死区 (ce point dépasse le cadre de cet article).

Chaîne de portées : lorsque différentes portées sont piégées ensemble, une chaîne de portées est formée. Notez que la direction de recherche est de l’intérieur vers l’extérieur.

Pourquoi la direction de recherche est-elle de l'intérieur vers l'extérieur ? C'est une question intéressante. Personnellement, je pense que cela est déterminé par la manière dont les fonctions d'exécution js sont poussées dans la pile (cela semble un peu hors sujet, les amis intéressés peuvent vérifier les informations).

Portée lexicale

La raison pour laquelle une fonction peut accéder à des variables dans une autre portée de fonction (ou se souvenir de la portée actuelle et y accéder en dehors de la portée actuelle) est 关键点 🎜>Cela fonctionne. C’est très important, mais tout le monde ne connaît pas ce point de connaissance. Discutons-en brièvement ici. 词法作用域

Dans le monde de la programmation, il existe deux modes de fonctionnement de la portée, l'un est

utilisé par la plupart des langages de programmation ; l'autre est l'opposé 词法作用域 (ce n'est pas la portée de cet article). 动态作用域

Portée lexicale : la portée des variables et des blocs a été déterminée lorsque vous écrivez le code, et ne changera pas avec l'objet ou le lieu appelant (on a l'impression que c'est le contraire).

Que diriez-vous de donner un exemple :

let a = 1;
function fn(){
    let a = 2;
    function fn2(){
        console.log(a);
    }
 return fn2;
}

let fn3 = fn();
fn3();

D'après la définition ci-dessus, nous pouvons savoir que

est une fonction de fermeture, et fn a obtenu fn3 L'adresse du pointeur de , lorsque fn2 est exécuté, il exécute en fait fn3, et la variable fn2 à l'intérieur, selon les règles de recherche de la chaîne de portée, est la variable a dans la portée fn, donc la finale la sortie est 2, pas 1. (Vous pouvez voir l'image ci-dessous)a

Découvrons ensemble les fermetures
Hors sujet, comment tricher sur la portée lexicale ?

Bien que la portée lexicale soit statique, il existe encore des moyens de la tromper et d'obtenir des effets dynamiques.

第一种方法是使用eval. eval可以把字符串解析成一个脚本来运行,由于在词法分析阶段,无法预测eval运行的脚本,所以不会对其进行优化分析。

第二种方法是with. with通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。with本身比较难掌握,使用不当容易出现意外情况(如下例子),不推荐使用 -.-

function Fn(obj){
    with(obj){
        a = 2;
    }
}

var o1 = {
    a:1
}
var o2 = {
    b:1
}

Fn(o1);
console.log(o1.a); //2
Fn(o2);
console.log(o2.a); //undefined;
console.log(a); //2 a被泄漏到全局里面去了
// 这是with的一个副作用, 如果当前词法作用域没有该属性,会在全局创建一个

闭包能干啥?

闭包的使用场景可多了,平时使用的插件或者框架,基本上都有闭包的身影,可能您没留意过罢了。下面笔者列举一些比较常见的场景。

  1. 模拟私有变量和方法,进一步来说可以是模拟模块化;目前常用的AMD,CommonJS等模块规范,都是利用闭包的思想;

  2. 柯里化函数或者偏函数;利用闭包可以把参数分成多次传参。如下面代码:

// 柯里化函数
function currying(fn){
    var allArgs = [];

    function bindCurry(){
        var args = [].slice.call(arguments);
        allArgs = allArgs.concat(args);
        return bindCurry;
    }
    bindCurry.toString = function(){
        return fn.apply(null, allArgs);
    };

    return bindCurry;
}
  1. 实现防抖或者节流函数;

  2. 实现缓存结果(记忆化)的辅助函数:

// 该方法适合缓存结果不易改变的函数
const memorize = fn => {
    let memorized = false;
    let result = undefined;
    return (...args) => {
        if (memorized) {
            return result;
        } else {
            result = fn.apply(null,args); 
            memorized = true;
            fn = undefined;
            return result;
        }
    };
};

如何区分闭包?

说了那么多,我怎么知道自己写的代码是不是闭包呢?先不说新手,有些代码的确隐藏的深,老鸟不仔细看也可能发现不了。 那有没有方法可以帮助我们区分一个函数是不是闭包呢?答案是肯定的,要学会善于利用周边的工具资源,比如浏览器。

打开常用的浏览器(chrome或者其他),在要验证的代码中打上debugger断点,然后看控制台,在scope里面的Closure(闭包)里面是否有该函数(如下图)。

Découvrons ensemble les fermetures

闭包真的会导致内存泄漏?

答案是有可能。内存泄漏的原因在于垃圾回收(GC)无法释放变量的内存,导致运行一段时候后,可用内存越来越少,最终出现内存泄漏的情况。常见的内存泄漏场景有4种:全局变量;闭包引用;DOM事件绑定;不合理使用缓存。其中,闭包导致内存泄漏都是比较隐蔽的,用肉眼查看代码判断是比较难,我们可用借助chrome浏览器的Memory标签栏工具来调试。由于篇幅问题,不展开说明了,有兴趣自己去了解一下如何使用。

想了解更多编程学习,敬请关注php培训栏目!

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:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer