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


75

我正在使用Twitter的Typeahead。我正在从Intellij遇到此警告。这导致每个链接的“ window.location.href”成为我的项目列表中的最后一个项目。

如何修复我的代码?

下面是我的代码:

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

在@cdhowie的帮助下,还有更多代码:我将更新updater以及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))};

Answers:


61

你需要在这里筑巢两个功能,创建一个新的闭包捕获变量(而不是变量本身)的值,此刻正在创建的闭包。您可以使用立即调用的外部函数的参数来执行此操作。替换此表达式:

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

有了这个:

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

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

(请注意,您可以将其重命名inner_target为,target然后就可以了-target将使用最接近的参数,这将是函数参数。但是,在如此狭窄的范围内使用两个具有相同名称的变量可能会造成混淆,因此我将其命名为在我的示例中,它们的显示方式有所不同,因此您可以了解发生了什么。)


那第二部分呢?我在哪里为按钮设置href?这种处理方式对我来说似乎很奇怪= /
iCodeLikeImDrunk

3
@yaojiang在那里应用相同的技术。如果您需要帮助,请告诉我,我们可以聊天。但是首先尝试理解我的示例中发生的事情,然后尝试自己完成。(如果您自己动手,这个概念会更好!)
cdhowie

2
重要的是要注意,匿名函数通过引用围绕外部变量关闭。如果要按值封闭它们,则需要引入一个新变量,该变量具有要捕获的值,并且不会更改-由于JavaScript没有块作用域,因此最容易实​​现使用功能参数。
cdhowie

2
@yaojiang这不是JavaScript独有的技术,而是在具有功能范围的功能语言中广泛使用。看起来很奇怪,但功能也非常强大。给这些概念(尤其是闭包)一些时间,您将成为一个更好的程序员。(我不了解C#匿名方法是闭包,直到对JavaScript进行了广泛研究之后,当我回到C#时才意识到我已经错过了一个非常强大的工具。)
cdhowie

这给了我学习更多高级javascript(包括闭包)的动力,感谢您的这段代码!!!
泰特斯·沙特斯

147

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

它解释了三种方法。

在循环内使用闭包的错误方法

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

带有匿名包装的解决方案1

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

解决方案2-从闭包返回函数

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

解决方案3,我最喜欢的地方,我认为我终于明白了bind-是的!绑定FTW!

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

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

ps:如果您的大脑没有融化,那么您当天就没有足够的Javascript。



2

由于JavaScript的唯一作用域是函数作用域,因此您只需将闭包移动到外部函数即可,而不必位于所涉及的作用域之外。


0

只是为了澄清@BogdanRuzhitskiy的答案(因为我不知道如何在注释中添加代码),使用let的想法是在for块内创建一个局部变量:

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

除了IE11,这几乎可以在任何现代浏览器中使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.