Die veränderbare Variable ist vom Schließen aus zugänglich. Wie kann ich das beheben?

74

Ich benutze Typeahead von Twitter. Ich stoße auf diese Warnung von Intellij. Dies führt dazu, dass "window.location.href" für jeden Link das letzte Element in meiner Elementliste ist.

Wie kann ich meinen Code reparieren?

Unten ist mein 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);
            });
        }
    }
};

Mit @ cdhowies Hilfe noch etwas Code: Ich werde den Updater und auch die href für den click () aktualisieren.

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))};
iCodeLikeImDrunk
quelle

Antworten:

61

Sie müssen hier zwei Funktionen verschachteln und einen neuen Abschluss erstellen, der den Wert der Variablen (anstelle der Variablen selbst) zum Zeitpunkt des Abschlusses erfasst . Sie können dies mithilfe von Argumenten für eine sofort aufgerufene äußere Funktion tun. Ersetzen Sie diesen Ausdruck:

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;
}

Mit diesem:

(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))

Beachten Sie, dass wir targetin die äußere Funktion übergehen , die zum Argument inner_targetwird und den Wert von targetzum Zeitpunkt des Aufrufs der äußeren Funktion effektiv erfasst . Die äußere Funktion gibt eine innere Funktion zurück, die inner_targetanstelle von verwendet targetwird und inner_targetsich nicht ändert.

(Beachten Sie, dass Sie umbenennen inner_targetzu targetund Sie werden in Ordnung sein - in der Nähe target. Verwendet werden, welche die Funktionsparameter wäre jedoch mit zwei Variablen mit dem gleichen Namen in einem so engen Rahmen sehr verwirrend sein könnte und so ich genannt habe sie in meinem Beispiel anders, damit Sie sehen können, was los ist.)

cdhowie
quelle
Was ist mit dem zweiten Teil? wo setze ich die href für den button? Diese Art Dinge zu tun scheint mir super komisch = /
iCodeLikeImDrunk
3
@yaojiang Wende dort die gleiche Technik an. Wenn Sie Hilfe benötigen, lassen Sie es mich wissen und wir können zum Chat übergehen. Aber versuchen Sie zuerst zu verstehen, was in meinem Beispiel passiert, und versuchen Sie es selbst. (Das Konzept wird besser bleiben, wenn Sie es selbst tun!)
cdhowie
2
Es ist wichtig zu beachten, dass anonyme Funktionen um äußere Variablen als Referenz schließen . Wenn Sie sie nach Wert schließen möchten, müssen Sie eine neue Variable einführen, die den Wert hat, den Sie erfassen möchten, und der sich nicht ändert. Dies ist aufgrund der Tatsache, dass JavaScript keinen Blockbereich hat, am einfachsten zu implementieren mit Funktionsparametern.
CDhowie
2
@yaojiang Dies ist keine für JavaScript einzigartige Technik, sondern wird häufig in funktionalen Sprachen mit Funktionsumfang verwendet. Es scheint seltsam, ist aber auch unglaublich mächtig. Geben Sie den Konzepten (insbesondere dem Abschluss) etwas Zeit, um sich zu vertiefen, und Sie werden ein besserer Programmierer dafür sein. (Ich wusste nicht, dass anonyme C # -Methoden geschlossen wurden, bis ich eine umfangreiche Zeit mit JavaScript verbracht hatte, und als ich zu C # zurückkam, wurde mir klar, dass ich ein unglaublich leistungsfähiges Tool verpasst hatte.)
cdhowie
Dies hat mir einen Schub gegeben, mehr fortgeschrittenes Javascript einschließlich Schließungen zu lernen, danke für diesen Ausschnitt !!!
Titus Shoats
146

Ich mochte den Absatz Closures Inside Loops von Javascript Garden

Es werden drei Möglichkeiten erläutert.

Die falsche Verwendung eines Verschlusses innerhalb einer Schleife

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

Lösung 1 mit anonymem Wrapper

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

Lösung 2 - Rückgabe einer Funktion aus einem Verschluss

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

Lösung 3 , mein Favorit, wo ich glaube, ich habe es endlich verstanden bind- yaay! FTW binden!

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

Ich kann den Javascript-Garten nur empfehlen - er hat mir diese und viele weitere Javascript-Macken gezeigt (und mich dazu gebracht, JS noch mehr zu mögen).

ps Wenn dein Gehirn nicht geschmolzen ist, hattest du an diesem Tag nicht genug Javascript.

Maciej Jankowski
quelle
2

Da der einzige Bereich, über den JavaScript verfügt, der Funktionsumfang ist , können Sie den Abschluss einfach auf eine externe Funktion außerhalb des Bereichs verschieben, in dem Sie sich befinden.

Oded Breiner
quelle
0

Um die Antwort von @BogdanRuzhitskiy zu verdeutlichen (da ich nicht herausfinden konnte, wie der Code in einen Kommentar eingefügt werden kann), besteht die Idee bei der Verwendung von let darin, eine lokale Variable innerhalb des for-Blocks zu erstellen:

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

Dies funktioniert in so ziemlich jedem modernen Browser außer IE11.

ivanhoe
quelle