为一个未显示的局部变量定义Setter / Getter:不可能?

时间:2011-09-13 10:20:04

标签: javascript scope closures local setter

以前有一些关于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,它在任何窗口创建之前运行,其中上下文是实际的全局上下文,甚至尝试直接在全局上下文中调用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中不起作用,上面的方法可以。)

所以基本上可以为对象上的任何变量定义get / set guard,包括直接调用对象的窗口/全局上下文(你不需要window.propname,只需要propname )。这是无法引用未填充的范围变量的问题,是唯一可以在可访问范围但没有可寻址容器的类型。当然,它们也是最常用的,所以它不是一个优势。这个问题也超越了ES6 / Harmony中当前Proxies的实现,因为它是一个特别的问题,因为它无法用语言的语法来处理本地对象的容器。

我希望能够做到这一点的原因是它是允许重载大多数数学运算符以用于复杂对象(如数组和散列)并导出复杂结果值的唯一障碍。如果在我为重载设置的对象类型上设置了值,我需要能够挂钩到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

我不在乎解决方案是多么狡猾,在这一点上,即使它需要打破现有的每一个最佳实践,它对我是否能够完成是一种强烈的好奇心。到目前为止,我已经通过重新定义/拦截/修改Object.prototype.valueOf/toStringFunction.prototype Function.prototype.constructorFunction.prototype.call/apply,{ {1}}等等,具有无限的递归错误,并试图追溯钻井平台上下文。我能够完成的唯一工作就是用eval基本上包装整个事物并动态构建代码块,这对我来说实际上是一个太过于实际使用的桥梁。唯一的另一个远程成功路线是使用arguments.callee.caller结合预先定义容器上的所有局部变量,但这显然是在使用with的问题上非常具有侵扰性。

4 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分:6)

目前在具有Proxies的环境中可以实现。那将是节点> 0.6运行node --harmony_proxies或> 0.7运行node --harmony。 Chromium Canary(不确定它是否已经过了)关于:底部的标志,实验性的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 :(得分:1)

由于您声明想要与window/global类似的行为,因此我假设您希望在window/global之外的给定上下文中执行此操作。一种简单的方法是将with语句与local对象和define函数结合使用,该函数以Object.defineProperty为目标来实现local。您不只是将自己的代码放在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"

除了支持get/setters以及枚举和写访问权限选项以外,另一种定义属性的方法是使用define函数。预期与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上的信息来查看它是否适合您的情况。

答案 3 :(得分:0)

我不知道这是否能回答您的问题,但这可以起作用:

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

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

configurable: true});
相关问题