为无父局部变量定义 Setter/Getter:不可能?

Posted

技术标签:

【中文标题】为无父局部变量定义 Setter/Getter:不可能?【英文标题】:Defining Setter/Getter for an unparented local variable: impossible? 【发布时间】:2011-11-16 01:08:03 【问题描述】:

以前有几个关于 *** 的问题询问如何通过作用域链访问局部变量,例如,如果您想使用括号表示法和字符串引用局部变量,则需要 __local__["varName"] 之类的东西。到目前为止,我什至还没有找到实现这一点的最骇人听闻的方法,而且在利用我所知道的每一个技巧数小时后,我还没有想出一个方法。

它的目的是在任意无父变量上实现 getter/setter。 Object.defineProperties 或__defineGet/Setter__ 需要调用上下文。对于全局或窗口上下文中的属性,您可以实现使用 setter/getter 直接引用对象的目标。

Object.defineProperty(this, "glob", get: function()return "direct access")
console.log(glob); //"direct access"

即使在使用自定义扩展的测试中,我也编译成修改后的 Chromium,该 Chromium 在上下文是实际全局上下文的任何窗口创建之前运行,甚至尝试在全局上下文中直接调用 this崩溃 我的程序,我可以顺利完成:

Object.defineProperty(Object.prototype, "define", 
    value: function(name, descriptor)
        Object.defineProperty(this, name, descriptor);
    
;
define("REALLYglobal", get: function() return "above window context"; );

然后它在以后创建的所有框架中都可用,作为通过指定 getter/setter 路由的全局路由。旧的 __defineGet/Setter__ 也可以在该上下文中工作,但没有指定调用它的对象(虽然在 Firefox 中不起作用,但上面的方法可以)。

所以基本上可以为对象上的任何变量定义获取/设置保护,包括直接调用对象的窗口/全局上下文(您不需要window.propname,只需propname)。这是无法引用无父范围变量的问题,这是唯一可以在可访问范围内但没有可寻址容器的类型。当然,它们也是最常用的,所以它不是极端情况。这个问题也超越了 ES6/Harmony 中代理的当前实现,因为它是一个无法使用语言语法处理本地对象容器的问题。

我希望能够这样做的原因是,它是允许重载大多数数学运算符以用于复杂对象(如数组和哈希)并导出复杂结果值的唯一障碍。在我为重载设置的对象类型上设置值的情况下,我需要能够连接到 setter。如果对象可以是全局的或者可以包含在父对象中,那没问题,这可能就是我将要使用的。 a.myObject 仍然有用,但目标是使其尽可能透明地可用。

不仅如此,能够完成这样的事情真的很有用:

var point3d = function()
    var x, y, z;
    return 
        get: function() return [x, y, z]; ,
        set: function(vals) x=vals[0]; y=vals[1]; z=vals[2]; 
    ;
;

(这类似于 ES6 的解构,但具有更通用的应用程序来实现附加到获取/设置的功能,而不仅仅是传输复杂值)。即使是这个基本代码也会完全失败:

var x = myname: "intercept valueOf and :set: to overload math ops!", index: 5;
x++; //x is now NaN if you don't implement a setter somehow

我不在乎这个解决方案有多么老套,在这一点上,我只是对它是否可以完成产生了强烈的好奇心,即使它需要打破所有存在的最佳实践。到目前为止,我已经让 Firefox 和 Chrome 崩溃了几百次,通过无限递归执行诸如重新定义/拦截/修改Object.prototype.valueOf/toStringFunction.prototypeFunction.prototype.constructorFunction.prototype.call/applyarguments.callee.caller 等操作错误和诸如此类的尝试追溯性地对上下文进行陪审团。我唯一能做的就是用 eval 和动态构建代码块来包装整个事情,这对我来说太远了,以至于我无法实际使用。唯一其他远程成功的方法是使用with 并结合预先定义容器上的所有局部变量,但这显然对使用with 的问题非常具有侵入性。

【问题讨论】:

object.defineProperty 应该在 window 上工作。但是,您无法创建局部变量,因为您无法以编程方式访问 ScopeContext 您意识到管理此问题的唯一方法是正确编译。 (My attempt at compilation) 这是我最后一次尝试,看看是否有人发现了我没有发现的漏洞。我有以某种方式找到解决方案的历史,这是我在 *** 上的第一个问题,因为经过大量工作后我最终还是一无所获。 出于好奇,您为什么要这样做?是否存在导致这种情况的现实问题? 它显然可以解决,但它似乎是一个明显的漏洞,确实会导致特定问题。我看到人们问的最常见的问题是尝试使用括号表示法访问局部变量,这可能非常有用。它只是通过要求在父成员或全局上下文中(在语法上)完成所有真正有用/很酷的技巧来强制对事物采取更加面向类/对象的角度。 【参考方案1】:

这目前在具有代理的环境中是可能的。那将是节点 > 0.6 以 node --harmony_proxies 运行或 >0.7 以 node --harmony 运行。 Chromium Canary(不确定它是否还没有)在底部的 about:flags 中,实验性 javascript。 Firefox 已经有一段时间没有标志了。

所以当 ES6 变得更正式时,这可能不会起作用,但现在它在一定程度上起作用了。

  var target = (function()
    var handler = Proxy.create(Proxy.create(
      get: function(r, trap)
        return function(name,val,c,d)
          if (trap === 'get' || trap === 'set') 
            name = val;
            val = c;
          
          console.log('"'+trap + '" invoked on property "'+name+'" ' + (val?' with value "'+val+'"':''));
          switch (trap) 
            case 'get': return target[name];
            case 'set': return target[name] = val;
            case 'has': return name in target;
            case 'delete': return delete target;
            case 'keys': return Object.keys(target);
            case 'hasOwn': return Object.hasOwnProperty.call(target, name);
            case 'getPropertyDescriptor':
            case 'getOwnPropertyDescriptor': return Object.getOwnPropertyDescriptor(target, name);
            case 'getPropertyNames':
            case 'getOwnPropertyNames': return Object.getOwnPropertyNames(target);
            case 'defineProperty': return Object.defineProperty(target, name, val);
          
        
      
    ))

    var target = 
      x: 'stuff',
      f:  works: 'sure did' ,
      z: ['overwritten?']
    ;


    with (handler)
      var z = 'yes/no';
      if (x) 
        //x
       else 
        x = true;
      
      console.log(f.works);
      if (f.works) 
        f.works = true;
        delete f;
      

    
    return target
  )()
   // "getPropertyDescriptor" invoked on property "z" 
   // "getPropertyDescriptor" invoked on property "z" 
   // "getPropertyDescriptor" invoked on property "x" 
   // "get" invoked on property "x" 
   // "getPropertyDescriptor" invoked on property "console" 
   // "getPropertyDescriptor" invoked on property "f" 
   // "get" invoked on property "f" 
   // sure did
   // "getPropertyDescriptor" invoked on property "f" 
   // "get" invoked on property "f" 
   // "getPropertyDescriptor" invoked on property "f" 
   // "get" invoked on property "f" 
   // "getPropertyDescriptor" invoked on property "f" 

   target:  x: 'Stuff', f:  works: true ,  z: ['overwritten?'] 

命中或未命中,您需要注意不要通过简单地查看调试器中的代理来炸毁浏览器。我不得不将那个东西包装在一个闭包中,以防止代理最终进入全局范围,否则它每次都会使框架崩溃。关键是它在某种程度上起作用,而其他任何东西都不起作用。

【讨论】:

【参考方案2】:

看起来答案是。我一直在寻找这样的行为很长一段时间。我一直无法想出任何可行的解决方案。 This SO question 看起来很相似。 Python 有很好的 locals 关键字。

【讨论】:

接受了最有可能不可能的答案。【参考方案3】:

由于您声明您想要与window/global 类似的行为,我假设您希望在除window/global 之外的给定上下文中执行此操作。一个简单的方法是使用with 语句与local 对象和define 函数结合使用Object.definePropertylocal 作为目标。您不仅仅是将自己的代码放在 with 块中。

重要提示:with 重载本机局部变量 (var, let, const)。因此,保持清晰的代码并防止在范围和父/子上下文中出现重复名称非常重要。

让我们从上下文开始,在本例中我使用闭包,但这也可以是函数、构造函数或任何其他上下文。

// This closure represents any function, class or other scoped block.
(function ()

());

接下来我们添加存储容器和define 函数。如果您想从代码中的任何位置(在此范围内)访问本地属性,这基本上是您应该始终开始的。

// This is where we store the local property. (except: var, let, const)
const local = ;

// The define function is used to declare and define the local properties.
function define(name, descriptor) Object.defineProperty(local, name, descriptor); 

现在您可以在with 语句之前放置任何代码,但对于本示例,我们将仅添加以某种方式需要local 的代码,因此下一步是创建with 语句。

// This with statement extends the current scope with local.
with(local)

    // This is where your code goes.


现在with语句的外部结构已经准备好了,我们可以开始在with语句中添加代码了。

放置在with 语句块中的所有代码都可以访问local 的属性,就好像它们在例如var 中定义一样,包括在with 语句中定义的属性。

有几种方法可以使用local 的属性。定义属性的最简单方法是直接在“本地”中设置它。这只需要做一次,之后该属性就可以通过它的名字来访问了。

local.setDirectly = "directly set value";

console.log(setDirectly);    // logs "directly set value"

定义属性的另一种方法是使用define 函数,而不是支持get/setters 以及可枚举和写访问选项。期待与 Object.defineProperty 相同的行为。

例如,您可以添加一个返回当前时间的 time 属性。

define("time", 
    get: function()
        var date = new Date();
        return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
    
)

console.log(time);

或者您可以创建一个计数器属性,每次访问时都会递增,并将其放置在嵌套闭包中,以保护计数器自己的变量免受不必要的更改。

(function ()
    var counterValue = 0;
    define("count", get: function() return counterValue++ );
());

console.log(count);          // logs 0
console.log(count);          // logs 1

当你将所有这些结合起来时,你会得到类似于以下代码的东西

// This closure represeents any function, class or other scoped block.
(function()
    // This is where we store the local property. (except: var, let, const)
    const local = ;

    // The define function is used to declare and define the local properties.
    function define(name, descriptor) Object.defineProperty(local, name, descriptor); 

    // This with statement extends the current scope with local.
    with(local)
        // This is where your code goes.

        // Defining a variable directly into local.
        local.setDirectly = "directly set value";
        console.log(setDirectly);    // logs "directly set value"
        // Defining local properties with the define function
        // For instance a time variable that return the current time (Hours:Minutes)
        define("time", 
            get: function()
                var date = new Date();
                return date.getHours() + ":" + ("0" + date.getMinutes()).substr(-2);
            
        )
        console.log(time);           // logs HH:MM

        // Or a counter property that increments each time it's been accessed.
        (function ()
            var counterValue = 0;
            define("count", get: function() return counterValue++ );
        ());
        console.log(count);          // logs 0
        console.log(count);          // logs 1
        console.log(count);          // logs 2
        console.log(count);          // logs 3
    
());

就像我之前提到的,理解使用with 语句的含义很重要。有关with 的更多信息,请访问MDN - with。正如问题所述,这是对你如何做的搜索,而不是你应该如何做的搜索。使用 MDN 上的信息,看看它是否适合您的情况。

【讨论】:

【参考方案4】:

我不知道这是否能回答您的问题,但这有效:

Object.defineProperty(window, 'prop', 
get: function () 
    alert('you just got me')
,

set: function (val) 
    alert('you just set me')
,

configurable: true);

【讨论】:

【参考方案5】:

对于仅使用基本对象的解决方案:

function ref (o)

    return new Proxy(, new Proxy(, 
        get (_, prop)  return (_, ...args) => Reflect[prop](o(), ...args) 
    ));

也可以使用 DOM 和原始对象:

function ref (o)

    return new Proxy(, new Proxy(, 
        get (_, prop)  return 
            get (_, prop) 
                let p = o(), r = p[prop];
                if (r instanceof Function) r = r.bind(p)
                return r 
            ,
            set (_, prop, v)  o()[prop] = v ,
            has (_, prop)  return prop in o() ,
            keys(_, prop)  return Object.keys(o()) ,
            apply (_, _this, args)  return Object.apply(o(), _this, args) ,
            hasOwn (_, prop)  return Object.hasOwnProperty.call(o(), prop) ,
            ownKeys() 
                var p = o();
                return Object.getOwnPropertyNames(p).concat(Object.getOwnPropertySymbols(p))
            ,
            deleteProperty (_, prop)  return delete o()[prop] ,
            defineProperty (_, prop, desc)  return Object.defineProperty(o(), prop, desc) ,
            getOwnPropertyDescriptor (_, prop)  return Object.getOwnPropertyDescriptor(o(), prop) 
        [prop] ?? ((_, ...args) => Reflect[prop](o(), ...args));
    ));


function refs (o)

    if (!(o instanceof Function)) o = (o => () => o)(o);
    return new Proxy(, 
        get (_, prop)  return ref(() => o()[prop]) 
    )

用法

let vec = x: 0, y: 1, z: 2;
let x, y, z = refs(() => vec);
outp(`X: $x. Y: $y. Z: $z`); // X: 0. Y: 1. Z: 2
vec.x = 3;
outp(`X: $x. Y: $y. Z: $z`); // X: 3. Y: 1. Z: 2
x = 1;
outp(vec.x); // 3

vec = y: 1, z: 1;
outp(y === 1); // false
outp(y == 1); // true
outp(z == 1); // true
outp(y == z); // false
// You cannot directly compare these Proxy objects.
outp(y.valueOf() === z.valueOf()); // true
outp(y.valueOf() === 1); // true
outp(z.valueOf() === 1); // true

function ref (o)

    return new Proxy(, new Proxy(, 
        get (_, prop)  return 
            get (_, prop) 
                    let p = o(), r = p[prop];
                if (r instanceof Function) r = r.bind(p)
                return r 
            ,
            set (_, prop, v)  o()[prop] = v ,
            has (_, prop)  return prop in o() ,
            keys(_, prop)  return Object.keys(o()) ,
            apply (_, _this, args)  return Object.apply(o(), _this, args) ,
            hasOwn (_, prop)  return Object.hasOwnProperty.call(o(), prop) ,
            ownKeys() 
                var p = o();
                return Object.getOwnPropertyNames(p).concat(Object.getOwnPropertySymbols(p))
            ,
            deleteProperty (_, prop)  return delete o()[prop] ,
            defineProperty (_, prop, desc)  return Object.defineProperty(o(), prop, desc) ,
            getOwnPropertyDescriptor (_, prop)  return Object.getOwnPropertyDescriptor(o(), prop) 
        [prop] ?? ((_, ...args) => Reflect[prop](o(), ...args));
    ));


function refs (o)

    if (!(o instanceof Function)) o = (o => () => o)(o);
    return new Proxy(, 
        get (_, prop)  return ref(() => o()[prop]) 
    )


let text = '';
function emitText()

    document.body.appendChild(
        Object.assign(document.createElement('pre'),  innerText: text)
    );

function outp (t)

    text += " // " + t;

function header (t)

    emitText();
    document.body.appendChild(
        Object.assign(document.createElement('h1'),  innerText: t)
    );
    text = '';

function log (t)

    text += '\n' + t;


header("Usage");

let vec = x: 0, y: 1, z: 2; log('let vec = x: 0, y: 1, z: 2;');
let x, y, z = refs(() => vec); log('let x, y, z = refs(() => vec);');

log('outp(`X: $x. Y: $y. Z: $z`);'); outp(`X: $x. Y: $y. Z: $z`);
vec.x = 3; log('vec.x = 3;');
log('outp(`X: $x. Y: $y. Z: $z`);'); outp(`X: $x. Y: $y. Z: $z`);

x = 1; log('x = 1;');
log('outp(vec.x);'); outp(vec.x);
log('');
vec = y: 1, z: 1; log('vec = y: 1, z: 1;');
log('outp(y === 1);'); outp(y === 1);
log('outp(y == 1);'); outp(y == 1);
log('outp(z == 1);'); outp(z == 1);
log('outp(y == z);'); outp(y == z);
log('// You cannot directly compare these Proxy objects.');
log('outp(y.valueOf() === z.valueOf());'); outp(y.valueOf() === z.valueOf());
log('outp(y.valueOf() === 1);'); outp(y.valueOf() === 1);
log('outp(z.valueOf() === 1);'); outp(z.valueOf() === 1);

header('');

【讨论】:

以上是关于为无父局部变量定义 Setter/Getter:不可能?的主要内容,如果未能解决你的问题,请参考以下文章

getter和setter

错误:可写原子属性无法将合成的 setter/getter 与用户定义的 setter/getter 配对

Dart中类的getter和setter

Java中成员变量和属性之间的关系

笔试题2

为什么使用Getter和Setter?Getter和Setter有什么区别?