JavaScript闭包问题

时间:2009-09-23 13:42:43

标签: javascript closures

嘿伙计们,我被困在以下代码中。首先,我将描述用例:使用ColorGradient实例调用函数“addPreset”。当调用 this.listController.addItem(...)时,会提供名为 onSelect 的回调函数,每次触发listController-item上的onSelect-event时都会调用该函数。我想要做的是将对 GLab.ColorSlider.applyColorGradient(...)的调用包装到一个新的闭包中,以便指定 addPreset的“cg”参数值“* 将被“抓住”在其中。但它不起作用。

问题:现在每次调用 addPreset 时, cg (通过调用传递)的值将覆盖以前分配错误的所有值。但是, this.presetList 始终保持正确的值(我希望在闭包函数中捕获的值。即使插入匿名函数来破坏范围也无济于事。

请帮帮我。 :-)

谢谢,到目前为止

function addPreset(cg) {
    if (!(cg instanceof ColorGradient)) {
        throw new TypeError("PresetManager: Cannot add preset; invalid arguments received");
    }

    var newIndex = this.listController.addItem(cg.getName(), {
        onSelect: (function(cg2) {
            return function() {
                // addPreset's scope should now be broken
                GLab.ColorSlider.applyColorGradient(cg2);
                console.log(cg2);
            }
        })(cg)
    });

    this.presetList[newIndex] = cg;
}

@bobince:当然可以。

上面的代码片段是 PresetManager.js 的一部分,listController是类 ListWrapper.js

的实例

http://code.assembla.com/kpg/subversion/nodes/GradientLab/lib-js/PresetManager.js http://code.assembla.com/kpg/subversion/nodes/GradientLab/lib-js/ListWrapper.js

@Matt: cg是ColorGradient的一个实例。我自己的自定义类。此外,确保始终将“有效”值作为cg传递。 (当你有几分钟时,你可以将整个assembla repo下载为zip-archive。在FF> 3.5中解压并测试并启用Firebug控制台。)

3 个答案:

答案 0 :(得分:1)

答案可以在这个问题中找到:Doesn't JavaScript support closures with local variables?

答案 1 :(得分:0)

如果我错了,请有人纠正我,因为我仍然是JavaScript闭包和范围的新手。但在我看来,你拥有的包装匿名函数只是为它返回的函数提供一个适当的范围变量/闭包。这可以简化吗?

function addPreset(cg) {
            if (!(cg instanceof ColorGradient)) {
                throw new TypeError("PresetManager: Cannot add preset; invalid arguments received");
            }

            var closured = cg;
            var newIndex = this.listController.addItem(cg.getName(), {
                onSelect: function() {
                    // addPreset's scope should now be broken
                    GLab.ColorSlider.applyColorGradient(closured);
                    console.log(closured);
                }
            });

            this.presetList[newIndex] = cg;
        }

答案 2 :(得分:0)

只想告诉你,我终于自己解决了问题。我花了差不多2天(在业余时间)让它感到困惑,但我认为这值得。至少我的代码仍然优雅,我绝对得到了关闭的整个事情。我们来看一下:

我的错误代码


第1部分:

function addPreset(cg) {
    if (!(cg instanceof ColorGradient)) {
        throw new TypeError("PresetManager: blablabla");
    }
    // calls the function in Part 2
    var newIndex = this.listController.addItem(cg.getName(), {
        onSelect: (function(cg2) {
            return function() {
                // addPreset's scope should now be broken
                GLab.ColorSlider.applyColorGradient(cg2);
                console.log(cg2);
            }
        })(cg)
    });

    this.presetList[newIndex] = cg;
}

第2部分:

// The method being called by this.listController.addItem(..)
function addItem(caption, args) {
    var _this = this,
        currIndex,
        id,
        newItem
        itemSelectCb = (!!args && typeof args.onSelect == "function") ?
                            args.onSelect :
                            undefined;

    currIndex = this.numOfItems;
    id = this.ITEM_ID_PREFIX + currIndex;
    newItem = this.$itemTemplate
        .clone()
        .text(caption)
        .attr("id", id)
        .bind("click", function(e) {
            e.stopPropagation();
            if (typeof itemSelectCb != "undefined") {
                itemSelectCb();
            }    
            _this._onSelect($(".ListWrapperItem").index(this));
        })
        .appendTo(this.$container);

    this.numOfItems = $("." + this.DEFAULT_ITEM_CLASS, this.$container).length;

    return currIndex;
}


固定代码


错误发生在第2部分;当calld jQuery的bind-method用于添加click-event-listener时,我使用了匿名函数(= new closure),但引用了itemSelectCb ;所以匿名函数的范围与addItem 保持“连接”。每当我调用addItem时,另一个值被赋予toitemSelectCb,导致未知的副作用,所有对先前创建的匿名函数内的itemSelect的引用都指向该值。这意味着,所有匿名函数都使用最后指定的值。

要“破坏”范围,我所要做的就是修改第2部分的行,其中创建了jQuery绑定的事件处理程序。固定代码看起来像这样:

function addItem(caption, args) {
    var _this = this,
        currIndex,
        id,
        newItem
        itemSelectCb = (!!args && typeof args.onSelect == "function") ?
                            args.onSelect :
                            undefined;

    currIndex = this.numOfItems;
    id = this.ITEM_ID_PREFIX + currIndex;
    newItem = this.$itemTemplate
        .clone()
        .text(caption)
        .attr("id", id)
        .bind("click", (function(itemSelectCb) {    

            return function(e) {                    
                e.stopPropagation();
                if (typeof itemSelectCb != "undefined") {
                    itemSelectCb();
                }    
                _this._onSelect($(".ListWrapperItem").index(this));                    
            }

        })(itemSelectCb))
        .appendTo(this.$container);

    this.numOfItems = $("." + this.DEFAULT_ITEM_CLASS, this.$container).length;

    return currIndex;
}