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

Defining Setter/Getter for an unparented local variable: impossible?

本文关键字:Getter 不可能 Setter 定义 局部变量      更新时间:2023-09-26

之前在StackOverflow上有一些问题,质疑如何通过作用域链访问局部变量,例如如果您想使用括号符号和字符串引用局部变量,则需要类似__local__["varName"]。到目前为止,我还没有找到最黑客的方法来实现这一点,并且在利用我知道的每一个技巧数小时后还没有想出一种方法。

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

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"; }});

然后,它可用于以后创建的所有帧,作为通过指定吸气器/设置器的全局路由。旧__defineGet/Setter__也可以在该上下文中工作,而无需指定调用它的内容(尽管在 Firefox 中不起作用,但上面的方法可以(。

所以基本上可以为对象上的任何变量定义 get/set 保护,包括直接调用对象的窗口/全局上下文(你不需要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

我不在乎解决方案有多笨拙,在这一点上,我只是对它是否可以完成的强烈好奇心,即使它需要打破现有的每一个最佳实践。到目前为止,我已经通过重新定义/拦截/修改Object.prototype.valueOf/toStringFunction.prototype Function.prototype.constructorFunction.prototype.call/applyarguments.callee.caller等方式使 Firefox 和 Chrome 崩溃了几百次,并试图追溯地陪审钻机上下文。我唯一能够做的工作就是用评估和动态构建的代码块基本上包装整个东西,这是一个对我来说太远而无法实际使用的桥梁。唯一另一个远程成功的途径是将with与容器上预定义的所有局部变量结合使用,但这显然在使用with的问题之外非常具有侵入性。

这目前在具有代理的环境中是可能的。这将是节点> 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?'] }

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

看起来答案是否定的。我一直在寻找这样的行为已经有一段时间了。我无法想出任何过得去的解决方案。这个问题似乎很相似。Python有一个很好的locals关键字。

既然你声明你想要与window/global类似的行为,我假设你想要在给定的上下文中这样做,而不是window/global。执行此操作的一种简单方法是将 with 语句与 local 对象和define函数结合使用,这些函数以 local 为目标实现Object.defineProperty。您不仅仅是将自己的代码放在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 上的信息,看看它是否适合您的情况。

我不知道

这是否回答了你的问题,但这有效:

Object.defineProperty(window, 'prop', {
get: function () {
    alert('you just got me')
},
set: function (val) {
    alert('you just set me')
},
configurable: true});

对于仅包含基本对象的解决方案:

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('');