注意: 标题为 背景的小节中的材料 不是必需的。该问题的完整描述完全包含在前面的段落中。
我想实现一种通用、轻量级和“不显眼”的方式来“标记”任意对象。
更具体地说,我想定义(抽象)函数tag
、isTagged
和的等价物getTagged
,这样:
isTagged(t)
是true
当且仅当某个对象t
的返回值是, ;tag(o)
o
getTagged(tag(o))
等同于o
, 对于每个对象o
;- 如果
t = tag(o)
, 那么tag(t)
应该等同于t
; - 除了上面 (1)、(2) 和 (3) 中描述的行为以及涉及 的严格身份测试之外,
===
应该表现相同。tag(o)
o
[编辑:另一个要求是实现不应该Object
以任何方式修改类,也不应该修改任何其他标准类。]
例如:
>>> isTagged(o = "foo")
false
>>> isTagged(t = tag(o))
true
>>> getTagged(t) === o
true
>>> tag(t) === t
true
>>> t.length
3
>>> t.toUpperCase()
"FOO"
下面我将尽力解决这个问题。它(几乎)是通用的,但是,很快就会清楚,它绝不是轻量级的!!!(此外,它还没有完全满足上面的要求 4,所以它不像我想要的那样“不引人注目”。此外,我对它的“语义正确性”有严重怀疑。)
该解决方案包括o
用“代理对象”包装要标记的对象,并将所有属性(无论是“拥有”还是“继承”)p
复制到.o
p
我的问题是:
是否可以在不必复制标记对象的所有属性的情况下实现上述规范?
背景
这是上面提到的实现。它依赖于效用函数getProperties
,其定义(FWIW)在最后给出。
function Proxy (o) { this.__obj = o }
function isTagged(t) {
return t instanceof Proxy;
}
function getTagged(t) {
return t.__obj;
}
var tag = (function () {
function _proxy_property(o, pr) {
return (typeof pr === "function")
? function () { return pr.apply(o, arguments) }
: pr;
}
return function (o) {
if (isTagged(o)) return o;
if (typeof o.__obj !== "undefined") {
throw TypeError('object cannot be proxied ' +
'(already has an "__obj" property)');
}
var proxy = new Proxy(o);
var props = getProperties(o); // definition of getProperties given below
for (var i = 0; i < props.length; ++i) {
proxy[props[i]] = _proxy_property(o, o[props[i]]);
}
return proxy;
}
})();
这种方法虽然笨拙,但至少似乎有效:
// requirement 1
>>> isTagged(o = "foo")
false
>>> isTagged(p = tag(o))
true
// requirement 2
>>> getTagged(p) === o
true
// requirement 3
>>> tag(p) === p
true
// requirement 4
>>> p.length
3
>>> p.toUpperCase()
"FOO"
……嗯,差不多;并不总是满足要求 (4):
>>> o == "foo"
true
>>> p == "foo"
false
>>> o == o
true
>>> p == o
false
FWIW,getProperties
这是函数使用的tag
函数的定义。欢迎批评。(警告: 我是一个完全不知道自己在做什么的 JS 菜鸟! 使用此功能需要您自担风险!)
function getProperties(o) {
var seen = {};
function _properties(obj) {
var ret = [];
if (obj === null) {
return ret;
}
try {
var ps = Object.getOwnPropertyNames(obj);
}
catch (e if e instanceof TypeError &&
e.message === "obj is not an object") {
return _properties(obj.constructor);
}
for (var i = 0; i < ps.length; ++i) {
if (typeof seen[ps[i]] === "undefined") {
ret.push(ps[i]);
seen[ps[i]] = true;
}
}
return ret.concat(_properties(Object.getPrototypeOf(obj)));
}
return _properties(o);
}