简体   繁体   English

可以从闭包访问可变变量。 我怎样才能解决这个问题?

[英]Mutable variable is accessible from closure. How can I fix this?

I am using Typeahead by twitter.我在 Twitter 上使用 Typeahead。 I am running into this warning from Intellij.我遇到了来自 Intellij 的警告。 This is causing the "window.location.href" for each link to be the last item in my list of items.这导致每个链接的“window.location.href”成为我的项目列表中的最后一个项目。

How can I fix my code?我该如何修复我的代码?

Below is my 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);
            });
        }
    }
};

With @cdhowie's help, some more code: i will update the updater and also the href for the click()在@cdhowie 的帮助下,添加更多代码:我将更新更新程序以及 click() 的 href

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

I liked the paragraph Closures Inside Loops from Javascript Garden我喜欢Javascript Garden 中的Closures Inside Loops段落

It explains three ways of doing it.它解释了三种方法。

The wrong way of using a closure inside a loop在循环中使用闭包的错误方法

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

Solution 1 with anonymous wrapper使用匿名包装器的解决方案 1

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

Solution 2 - returning a function from a closure解决方案 2 - 从闭包中返回一个函数

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

Solution 3 , my favorite, where I think I finally understood bind - yaay!解决方案 3 ,我最喜欢的,我想我终于明白了bind - yaay! bind FTW!绑定FTW!

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

I highly recommendJavascript garden - it showed me this and many more Javascript quirks (and made me like JS even more).我强烈推荐Javascript 花园- 它向我展示了这一点以及更多 Javascript 怪癖(并使我更喜欢 JS)。

ps if your brain didn't melt you haven't had enough Javascript that day. ps 如果你的大脑没有融化,那你那天的 Javascript 还不够。

You need to nest two functions here, creating a new closure that captures the value of the variable (instead of the variable itself) at the moment the closure is created .你需要在这里筑巢两个功能,创建一个新的闭包捕获变量(而不是变量本身)的此刻的值创建的闭包 You can do this using arguments to an immediately-invoked outer function.您可以使用立即调用的外部函数的参数来执行此操作。 Replace this 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;
}

With this:有了这个:

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

Note that we pass target into the outer function, which becomes the argument inner_target , effectively capturing the value of target at the moment the outer function is called.请注意,我们将target传递给外部函数,它成为参数inner_target ,在调用外部函数时有效地捕获了target的值。 The outer function returns an inner function, which uses inner_target instead of target , and inner_target will not change.外部函数返回一个内部函数,它使用inner_target而不是target ,并且inner_target不会改变。

(Note that you can rename inner_target to target and you will be okay -- the closest target will be used, which would be the function parameter. However, having two variables with the same name in such a tight scope could be very confusing and so I have named them differently in my example so that you can see what's going on.) (请注意,您可以将inner_target重命名为target就可以了——将使用最接近的target ,这将是函数参数。但是,在如此紧凑的范围内拥有两个同名的变量可能会非常混乱,因此我在我的示例中对它们进行了不同的命名,以便您可以看到发生了什么。)

In ecmascript 6 we have new opportunities.在 ecmascript 6 中,我们有了新的机会。

The let statement declares a block scope local variable, optionally initializing it to a value. let语句声明了一个块作用域局部变量,可选择将其初始化为一个值。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

由于 JavaScript 的唯一作用域是函数作用域,因此您可以简单地将闭包移动到您所在作用域之外的外部函数。

Just to clarify on @BogdanRuzhitskiy answer (as I couldn't figure out how to add the code in a comment), the idea with using let is to create a local variable inside the for block:只是为了澄清@BogdanRuzhitskiy 的回答(因为我不知道如何在评论中添加代码),使用 let 的想法是在 for 块中创建一个局部变量:

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

This will work in pretty much any modern browser except IE11.这几乎适用于除 IE11 之外的所有现代浏览器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM