【问题标题】:Closure Not Keeping State Change关闭不保持状态变化
【发布时间】:2017-04-09 01:49:27
【问题描述】:

我以为我理解了闭包,但突然之间一些简单的事情似乎不起作用了。

我使用闭包创建了一个单例。这是一个非常缩小的版本:

    Foobar = function() {
        var data = null;

        function init(obj) {
            data = obj;
        }

        return {
            init: init,
            currentValue: data
        }
    }();

由于它是一个单例,我希望当我调用“init”函数时,“data”的值将在唯一存在的对象中更改,并且该值在后续检索中仍然可用'当前值'。

这是我的测试:

    Foobar.init({id:3});
    var test = Foobar.currentValue;

当我在 Firefox (F12) 中单步执行此代码时,我可以看到是的 - 'data' 对 init 函数是可见的,并且当我执行 'init' 时它正在更改该变量。但是,在退出 init 函数并返回执行测试的第二行后,var 'test' 被赋值为 null(创建单例时的原始值),而不是我传递给 'init' 函数的对象的副本,正如我所料。

有人能解开我的困惑吗?

【问题讨论】:

    标签: javascript


    【解决方案1】:

    开始
    var Foobar = function () {...}();
    

    语句可以分解为

    var Foobar;
    Foobar = function () {...}();
    

    在第一行,全局作用域注册 Foobar 时没有 rhs 引用,因此 Foobar 在立即调用函数表达式 (IIFE) 之前的任何位置都是未定义的。

    在第二行,IIFE 执行后,Foobar 被分配给 IIFE 的返回对象,如下所示。

    console.log(Foobar); // undefined
    var Foobar = function () {...}();
    console.log(Foobar); // Object {currentValue: null, init: function}
    

    此时IIFE的返回对象可以可视化为

    然后,当 Foobar.init({id:3}); 被执行时。 Foobar 的 init 将要求 rhs 引用,这恰好是 IIFE 的 init。 init 对数据有闭包并最终将 data 设置为 {id:3}

    因此

    var test = Foobar.currentValue;
    

    显然仍然是一个 null 值作为它的初始状态。

    使其符合我们的期望。正如anied 所提到的,这里至少有两种可能的解决方案。

    1. 在 IIFE 中创建另一个函数并关闭 data,以便 检索它们。
    2. IIFE 的 init 不是将 data 分配给新对象,而是直接 操纵同一个对象本身。

    【讨论】:

    • 我很欣赏图表。您还花时间回答问题,而不仅仅是修复错误。
    【解决方案2】:

    我认为您在这里混淆了两个不同的东西——也就是说,我认为您正在混淆“Javascript 通过引用传递对象”和“Javascript 闭包”。

    回答您的主要问题 - 问题是您在实例化时只返回一次变量的值,然后再也不返回。所以更新它不会改变任何东西。相反,如果您创建一个 返回 值的函数,您将看到它正在更新:

    Foobar = function() {
            var data = null;
    
            function init(obj) {
                data = obj;
            }
    
            function getCurrentValue() {
                return data; 
            }
      
    
            return {
                init: init,
                getCurrentValue: getCurrentValue
            }
        }();
        
        Foobar.init({id:3});
        console.log(Foobar.getCurrentValue());

    现在,if data 被初始化为 object,而您的 init 只是以某种方式扩展该对象,那么属性本身将是像您的第一个示例一样更新,因为它在整个生命周期中总是引用同一个对象:

    Foobar = function() {
            var data = {};
    
            function init(obj) {
                data.id = obj.id;
            }
    
            return {
                init: init,
                currentValue: data
            }
        }();
        
        Foobar.init({id:3});
        console.log(Foobar.currentValue);

    【讨论】:

    • SO 说要避免谢谢,但在这种情况下......太多次,SO 的答案集中在解决小例子或特殊情况下的错误,而没有回答问题。当这对下一个人没有帮助时,他们再次提出问题以获得对实际问题的一般性答案,他们会因重复的问题而受到指责。你实际上已经回答了这个问题并教会了我一些东西,而不是“修复错误”,这使得我以后不太可能重复这个错误。这些答案就是我重视 SO 的原因。
    【解决方案3】:

    问题是在返回的对象中放置数据会保存函数第一次执行时的值 - 这是 null。

    为了读取数据变量的当前值,currentValue 必须是一个函数,比如 init。

    Foobar = function() {
            var data = null;
    
            function init(obj) {
                    data = obj;
            }
    
            function currentValue() {
                    return data;
            }
    
            return {
                    init: init,
                    currentValue: currentValue
            }
    }();
    console.log(Foobar);
    Foobar.init({id:3});
    var test = Foobar.currentValue();
    console.log(Foobar,test);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-25
      相关资源
      最近更新 更多