【问题标题】:Unexpected behavior from variable reference in a closure闭包中变量引用的意外行为
【发布时间】:2014-12-23 19:47:28
【问题描述】:

当我希望返回的对象中的成员具有父作用域中定义的变量的正确值时,我遇到了一个问题。但是,这个成员的值永远不会改变,我必须创建一个 getter 方法来检索正确的值。举个简单的例子,下面是图的邻接矩阵表示的子集:

function AdjacencyMatrix() {
    // Here is the set of Vertices
    var V = [1, 2, 3];

    // Here's some functionality that will remove a vertex at some point,
    // right now we're just concerned with updating what V is equal to
    function removeVertex(v) {
        V.push(4);
        V = [];
        console.log(V);
    }

    // A "getter" method for the list of vertices
    function getVertices() {
      return V;   
    }

    // Ran when the Adjacency Matrix is initialized
    console.log(V);

    return Object.freeze({
        // Member that holds a reference to V
        vertices: V,

        // Methods that will be used later
        removeVertex: removeVertex,
        getVertices: getVertices
    });
}

// Initially logs [1, 2, 3], which is expected
var M = AdjacencyMatrix();

// Logs [], which is expected
M.removeVertex();

// Logs [1, 2, 3, 4], which is unexpected.
// Instead, it should log []
console.log(M.vertices);

// Logs [], which is expected
console.log(M.getVertices());

返回对象中的vertices 成员不应该始终保持对变量V 指向的内容的引用吗?相反,在此示例中,访问M 上的vertices 成员会维护对最初分配给变量V 的数组的引用,并忽略变量V 的任何重新分配。

或者,当将vertices 成员分配给V 时,它是否持有对V 值的引用,而不是变量所具有的任何值?

对不起,如果这个问题的措辞难以理解,我已尽力陈述我的期望和结果。

【问题讨论】:

    标签: javascript closures


    【解决方案1】:

    vertices: V, 表示:

    引用 Vvertices 属性的引用。

    就是这样 - 这不是一个闭包,它只是一个分配。

    因此,一旦您重新分配 V 以引用其他内容 - vertices 属性不会反映这一点,因为它存储了原始引用。

    【讨论】:

      【解决方案2】:

      M.vertices 不涉及闭包。

      在对象字面量中,每个键/值对的右侧是求值的,该求值的结果是分配给每个对象的属性的结果。

      执行时:

      return Object.freeze({
          // Member that holds a reference to V
          vertices: V,
      
          // Methods that will be used later
          removeVertex: removeVertex,
          getVertices: getVertices
      });
      

      V 被评估为 V 在那个时刻引用的数组。所以生成的对象引用了该数组,但它与V 没有任何联系。


      要获得您所描述的行为(如果那是您确实想要的)。您可以为 vertices 定义一个使用闭包的 getter 和 setter:
      var o = {
          // Methods that will be used later
          removeVertex: removeVertex,
          getVertices: getVertices
      }
      
      Object.defineProperty(o, 'vertices', {
          get: function () { return V; },
          set: function (val) { V = val; } // or you can omit the setter
                                           // to make the property readonly
      }); 
      
      return Object.freeze(o);
      

      【讨论】:

      • 我喜欢这个解释!不过我确实有一个问题,为什么M.vertices 的值会更新为[1, 2, 3, 4] 而不是留在[1, 2, 3]?在 removeVertex 方法中,我将值 4 推送到 M 上,当我访问 M.vertices 时会反映这种变化。
      • 因为你仍然持有一个引用,所以你可以对引用的对象进行变异并观察变异。
      • @JoshBlack 这就是 zerkms 所说的。评估表达式 V 不会创建一个全新的数组。它会产生对V 当时正在引用的数组的引用。如果之后修改了该数组,那么当您通过M.vertices 访问它时就会反映出来。
      【解决方案3】:

      Vertices 保存最初分配给它的 V 的值。如果您来自 C# 等面向对象的语言背景,这相当于“按值传递”(即仅传递值而不传递引用)。

      【讨论】:

      • 为什么不是真的?虽然在技术上不是“按值传递”,但从概念上讲,这种方式更容易理解。 V 的“值”分配给 Vertices 而不是它的引用。
      • 无论如何都不是“传值”。完全一样。它有一个特殊的名称“共享呼叫”。 “V 的‘值’分配给顶点,而不是它的参考。” --- 这完全是错误的,因为它完全相反:它是分配的引用。
      • 我还是不明白。如果是指派给 Vertices 的引用,那为什么 V 变化时不更新呢?
      • 是否 M.removeVertex();不变异吗?
      • 如果它被 M.removeVertex() 变异了;然后 console.log(M.vertices);应该记录 [] 而不是 [1,2,3,4]
      猜你喜欢
      • 2016-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多