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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16724620/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 05:45:44  来源:igfitidea点击:

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

javascriptclosuresmutable

提问by iCodeLikeImDrunk

I am using Typeahead by twitter. I am running into this warning from Intellij. This is causing the "window.location.href" for each link to be the last item in my list of items.

我在 Twitter 上使用 Typeahead。我遇到了来自 Intellij 的警告。这导致每个链接的“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))};

采纳答案by cdhowie

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 targetinto the outer function, which becomes the argument inner_target, effectively capturing the value of targetat the moment the outer function is called. The outer function returns an inner function, which uses inner_targetinstead of target, and inner_targetwill not change.

请注意,我们传入target了外部函数,它成为了参数inner_target,有效地捕获了target调用外部函数时的值。外部函数返回一个内部函数,它使用inner_target代替target,并且inner_target不会改变。

(Note that you can rename inner_targetto targetand you will be okay -- the closest targetwill 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_targettarget,您会没事的 -target将使用最接近的,这将是函数参数。但是,在如此严格的范围内拥有两个同名的变量可能会非常混乱,因此我命名为它们在我的示例中有所不同,以便您可以看到发生了什么。)

回答by Maciej Jankowski

I liked the paragraph Closures Inside Loopsfrom Javascript Garden

我喜欢的段落瓶盖内循环使用Javascript花园

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 1with 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! bind FTW!

解决方案 3,我最喜欢的,我想我终于明白了bind-耶!绑定FTW!

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

I highly recommend Javascript garden- it showed me this and many more Javascript quirks (and made me like JS even more).

我强烈推荐Javascript 花园- 它向我展示了这一点以及更多 Javascript 怪癖(并使我更喜欢 JS)。

p.s. if your brain didn't melt you haven't had enough Javascript that day.

ps 如果你的大脑没有融化,那你那天的 Javascript 还不够多。

回答by Bogdan Ruzhitskiy

In ecmascript 6 we have new opportunities.

在 ecmascript 6 中,我们有了新的机会。

The letstatement declares a block scope local variable, optionally initializing it to a value. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

语句声明块范围局部变量,选择将其初始化为一个值。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

回答by Oded Breiner

Since the only scoping that JavaScript has is functionscope, you can simply move the closure to an external function, outside of the scope you're in.

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

回答by ivanhoe

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 之外的所有现代浏览器。