web-dev-qa-db-fra.com

La variable mutable est accessible depuis la fermeture. Comment puis-je réparer cela?

J'utilise Typeahead par Twitter. Je rencontre cet avertissement d'Intellij. Cela provoque le "window.location.href" pour chaque lien pour être le dernier élément de ma liste d'éléments.

Comment puis-je réparer mon code?

Voici mon code:

AutoSuggest.prototype.config = function () {
    var me = this;
    var comp, options;
    var gotoUrl = "/{0}/{1}";
    var imgurl = '<img src="/icon/{0}.gif"/>';
    var target;

    for (var i = 0; i < me.targets.length; i++) {
        target = me.targets[i];
        if ($("#" + target.inputId).length != 0) {
            options = {
                source: function (query, process) { // where to get the data
                    process(me.results);
                },

                // set max results to display
                items: 10,

                matcher: function (item) { // how to make sure the result select is correct/matching
                    // we check the query against the ticker then the company name
                    comp = me.map[item];
                    var symbol = comp.s.toLowerCase();
                    return (this.query.trim().toLowerCase() == symbol.substring(0, 1) ||
                        comp.c.toLowerCase().indexOf(this.query.trim().toLowerCase()) != -1);
                },

                highlighter: function (item) { // how to show the data
                    comp = me.map[item];
                    if (typeof comp === 'undefined') {
                        return "<span>No Match Found.</span>";
                    }

                    if (comp.t == 0) {
                        imgurl = comp.v;
                    } else if (comp.t == -1) {
                        imgurl = me.format(imgurl, "empty");
                    } else {
                        imgurl = me.format(imgurl, comp.t);
                    }

                    return "\n<span id='compVenue'>" + imgurl + "</span>" +
                        "\n<span id='compSymbol'><b>" + comp.s + "</b></span>" +
                        "\n<span id='compName'>" + comp.c + "</span>";
                },

                sorter: function (items) { // sort our results
                    if (items.length == 0) {
                        items.Push(Object());
                    }

                    return items;
                },
// the problem starts here when i start using target inside the functions
                updater: function (item) { // what to do when item is selected
                    comp = me.map[item];
                    if (typeof comp === 'undefined') {
                        return this.query;
                    }

                    window.location.href = me.format(gotoUrl, comp.s, target.destination);

                    return item;
                }
            };

            $("#" + target.inputId).typeahead(options);

            // lastly, set up the functions for the buttons
            $("#" + target.buttonId).click(function () {
                window.location.href = me.format(gotoUrl, $("#" + target.inputId).val(), target.destination);
            });
        }
    }
};

Avec l'aide de @ cdhowie, un peu plus de code: je mettrai à jour le programme de mise à jour et aussi le href pour le clic ()

updater: (function (inner_target) { // what to do when item is selected
    return function (item) {
        comp = me.map[item];
        if (typeof comp === 'undefined') {
            return this.query;
        }

        window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);
        return item;
}}(target))};
71
iCodeLikeImDrunk

Vous devez imbriquer deux fonctions ici, créant une nouvelle fermeture qui capture la valeur de la variable (au lieu de la variable elle-même) au moment où la fermeture est créée. Vous pouvez le faire en utilisant des arguments pour une fonction externe immédiatement invoquée. Remplacez cette expression:

function (item) { // what to do when item is selected
    comp = me.map[item];
    if (typeof comp === 'undefined') {
        return this.query;
    }

    window.location.href = me.format(gotoUrl, comp.s, target.destination);

    return item;
}

Avec ça:

(function (inner_target) {
    return function (item) { // what to do when item is selected
        comp = me.map[item];
        if (typeof comp === 'undefined') {
            return this.query;
        }

        window.location.href = me.format(gotoUrl, comp.s, inner_target.destination);

        return item;
    }
}(target))

Notez que nous passons target dans la fonction externe, qui devient l'argument inner_target, capturant efficacement la valeur de target au moment où la fonction externe est appelée. La fonction externe renvoie une fonction interne, qui utilise inner_target au lieu de target et inner_target ne changera pas.

(Notez que vous pouvez renommer inner_target à target et tout ira bien - le target le plus proche sera utilisé, qui serait le paramètre de la fonction. Cependant, avoir deux variables avec le même nom dans une portée aussi étroite pourrait être très déroutant et je les ai donc nommées différemment dans mon exemple afin que vous puissiez voir ce qui se passe.)

62
cdhowie

J'ai aimé le paragraphe Fermetures à l'intérieur des boucles de Javascript Garden

Il explique trois façons de le faire.

La mauvaise façon d'utiliser une fermeture à l'intérieur d'une boucle

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

Solution 1 avec wrapper anonyme

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

Solution 2 - renvoyer une fonction à partir d'une fermeture

for(var i = 0; i < 10; i++) {
    setTimeout((function(e) {
        return function() {
            console.log(e);
        }
    })(i), 1000)
}

Solution 3 , mon préféré, où je pense avoir enfin compris bind - yaay! lier FTW!

for(var i = 0; i < 10; i++) {
    setTimeout(console.log.bind(console, i), 1000);
}

Je recommande fortement Javascript garden - cela m'a montré cela et bien d'autres bizarreries Javascript (et m'a fait aimer JS encore plus).

p.s. si votre cerveau n'a pas fondu, vous n'avez pas eu assez de Javascript ce jour-là.

140
Maciej Jankowski

Dans ecmascript 6, nous avons de nouvelles opportunités.

L'instruction let déclare une variable locale de portée de bloc, en l'initialisant éventuellement à une valeur. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

10
Bogdan Ruzhitskiy

Étant donné que la seule portée de JavaScript est fonction portée, vous pouvez simplement déplacer la fermeture vers une fonction externe, en dehors de la portée dans laquelle vous vous trouvez.

1
Oded Breiner