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

以前有几个关于 StackOverflow 的问题询问如何通过作用域链访问局部变量,例如,如果您想使用括号表示法和字符串引用局部变量,则需要 __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)
  • 这是我最后一次尝试,看看是否有人发现了我没有发现的漏洞。我有以某种方式找到解决方案的历史,这是我在 StackOverflow 上的第一个问题,因为经过大量工作后我最终还是一无所获。
  • 出于好奇,您为什么要这样做?是否存在导致这种情况的现实问题?
  • 它显然可以解决,但它似乎是一个明显的漏洞,确实会导致特定问题。我看到人们问的最常见的问题是尝试使用括号表示法访问局部变量,这可能非常有用。它只是通过要求在父成员或全局上下文中(在语法上)完成所有真正有用/很酷的技巧来强制对事物采取更加面向类/对象的角度。

标签: javascript scope closures local setter


【解决方案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('');

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-06-20
          • 1970-01-01
          • 1970-01-01
          • 2015-01-12
          • 1970-01-01
          • 2014-03-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多