无法使Object.defineProperty在循环中设置工作

时间:2013-08-04 12:03:38

标签: javascript

我需要设置一些动态setter并想使用defineProperty。

然而,我无法弄清楚为什么这不起作用。请注意,有人在this other question写道,他们认为它有效,但他们的例子对我来说也不起作用......

var a = { q:1, b:2, c:3, d:4, e:5, f:9 }

var iFaces = [ {}, {}, {}, {} ]

for( key in a )
{
    console.log( key );

    for( var l = iFaces.length - 1; l >= 0; --l )
    {
        (function( that, name )
        {
            Object.defineProperty
            (
                   that
                ,  name

                ,  {
                          enumerable  : true
                        , configurable: true
                        , set         : function( value ){ that[ name ] = value }

                    }
            )
        }
        )( iFaces[ l ], key )

        iFaces[ l ][ key ] = a[ key ]
    }
}

// output: RangeError: Maximum call stack size exceeded

必须以某种方式递归。

编辑:我尝试实现的功能是2个对象,可以使属性保持同步。所以我想创建一个通用的setter来更新姐妹对象,如果它没有相同的引用/值。

我需要解决方案符合ES5标准,似乎大多数替代的setter / getter都是依赖于实现的黑客。

3 个答案:

答案 0 :(得分:4)

set函数内部执行:

that[ name ] = value

会再次触发set函数,因为您将其绑定到name。您必须在set函数中使用不同的属性名称。例如:

that[ '__' + name ] = value

答案 1 :(得分:1)

您的set:函数导致递归,因为它导致写入set:函数已注册的同一属性,即

that[name] = value

将导致调用set函数,然后执行:

that[name] = value

将导致set函数被调用...

答案 2 :(得分:0)

我对defineProperty的理解是错误的。我以为它会让你在现有的属性上设置一个getter / setter。然后常识会认为setter只返回新值,或者如果用户必须在setter中设置实际属性,至少分配给该属性不会再次触发setter。

然而,并非使用defineProperty创建的那种功能。而是用于创建接口。您创建一个访问者属性,该属性本身不包含任何数据,但会从数据结构中公开某些其他数据,这些数据可能通过接口从客户端隐藏。这是一个强大的功能,如果您只是想快速添加验证到您的某个属性,这有点令人讨厌。

MDN对此并不十分清楚,因此可能会让人感到困惑,特别是因为defineProperty的“其他”用法是在包含值的属性上设置属性。 getter / setter不是这种属性的属性,而是与它分离。请注意,您的setter不会收到正在访问的属性的名称。因此,如果你想制作通用代码,你必须将它包装在匿名函数中以传递名称。如果你正在制作通用代码,你可能无论如何都要在循环中分配属性,所以你必须使用匿名功能是为了处理“封闭效应”无论如何。

我想用它来创建一个私有对象的公共接口。问题在于,如果客户端向接口上的属性分配内容,则会将其与私有对象断开连接,并且私有和公共代码将不再对相同的值执行操作。事实证明,在这种情况下,defineProperty工作的方式是点,因为我已经有一个私有(引用)对象,我想在另一个对象上创建访问器,允许访问这些数据而无需访问私有对象。

示例代码: 如果你想要一个通用的setter / getter(例如,在循环中分配并且实际代码存在于其他地方,所以你所有对象上的每个属性都不需要该函数的完整副本),你将不得不:

  1. 为属性的实际值创建辅助存储。

  2. 将一个匿名函数中的defineProperty调用包装起来处理闭包,并创建一个内联的getter / setter,它将使用属性的名称调用你的实际getter / setter。

  3. 这将按预期工作:

    var properties = { q:1, b:2, c:3 }
    
    var backends = [ {}, {} ]
    var iFaces   = [ {}, {} ]
    
    
    for( var key in properties )
    {
       for( var l = iFaces.length - 1; l >= 0; --l )
       {
          ;(function( iFace, backend, name )
          {
             Object.defineProperty
             (
                   iFace
                ,  name
    
                ,  {
                        enumerable  : true
                      , configurable: false
    
                      , set: function setter( value ){        _setter( backend, name, value ) }
                      , get: function getter(       ){ return _getter( backend, name        ) }
                   }
             )
    
          })( iFaces[ l ], backends[ l ], key )
    
          iFaces[ l ][ key ] = properties[ key ]
       }
    }
    
    
    function _setter( backend, name, value )
    {
       // do some validation
       //...
    
       backend[ name ] = value
    }
    
    
    
    function _getter( backend, name )
    {
       // keep access logs for example
       //...
    
       return backend[ name ]
    }
    
    
    iFaces[ 1 ].b = "Bananas, it works!"
    
    console.log( backends      );
    console.log( iFaces[ 1 ].b );
    
    // ouput:
    
    // [ { q: 1, b: 2, c: 3 },
    //   { q: 1, b: 'Bananas, it works!', c: 3 } ]
    // Bananas, it works!