为无父局部变量定义 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/toString
、Function.prototype
、Function.prototype.constructor
、Function.prototype.call/apply
、arguments.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.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"
定义属性的另一种方法是使用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:不可能?的主要内容,如果未能解决你的问题,请参考以下文章