2

注意: 标题为 背景的小节中的材料 不是必需的。该问题的完整描述完全包含在前面的段落中。

我想实现一种通用、轻量级和“不显眼”的方式来“标记”任意对象。

更具体地说,我想定义(抽象)函数tagisTagged和的等价物getTagged,这样:

  1. isTagged(t)true当且仅当某个对象t的返回值是, ;tag(o)o
  2. getTagged(tag(o))等同于o, 对于每个对象o;
  3. 如果t = tag(o), 那么tag(t)应该等同于t;
  4. 除了上面 (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复制到.op

我的问题是:

是否可以在不必复制标记对象的所有属性的情况下实现上述规范?


背景

这是上面提到的实现。它依赖于效用函数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

FWIWgetProperties这是函数使用的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);
}
4

2 回答 2

2

你把它复杂化了:

var tag = function(o) {
    Object.defineProperty(o, '__tagged', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: "static"
    });
    return o;
}

var isTagged = function(o) {
    return Object.getOwnPropertyNames(o).indexOf('__tagged') > -1;
}
于 2013-09-01T01:21:50.340 回答
2

我认为你把这一切都复杂化了。没有理由需要将标签存储在对象本身上。如果您创建一个使用对象指针作为键的单独对象,则不仅可以节省空间,而且如果任意对象碰巧具有名为“_tagged”的属性,则可以防止任何意外冲突。

var __tagged = {};

function tag(obj){
    __tagged[obj] = true;
    return obj;
}

function isTagged(obj){
    return __tagged.hasOwnProperty(obj);
}

function getTagged(obj){
    if(isTagged(obj)) return obj;
}

== 编辑 ==

所以我决定花一点时间来创建一个更强大的标记系统。这就是我创建的。

var tag = {
    _tagged: {},

    add: function(obj, tag){
        var tags = this._tagged[obj] || (this._tagged[obj] = []);
        if(tag) tags.push(tag);
        return obj;
    },

    remove: function(obj, tag){
        if(this.isTagged(obj)){
            if(tag === undefined) delete this._tagged[obj];
            else{
                var idx = this._tagged[obj].indexOf(tag);
                if(idx != -1) this._tagged[obj].splice(idx, 1);
            }
        }
    },

    isTagged: function(obj){
        return this._tagged.hasOwnProperty(obj);
    },

    get: function(tag){
        var objects = this._tagged
          , list = []
        ;//var

        for(var o in objects){
            if(objects.hasOwnProperty(o)){
                if(objects[o].indexOf(tag) != -1) list.push(o);
            }
        }

        return list;
    }
}

您不仅可以标记对象,而且实际上可以指定不同类型的标记并以列表的形式检索具有特定标记的对象。让我给你举个例子。

var a = 'foo'
  , b = 'bar'
  , c = 'baz'
;//var

tag.add(a);
tag.add(b, 'tag1');
tag.add(c, 'tag1');
tag.add(c, 'tag2');

tag.isTagged(a); // true
tag.isTagged(b); // true
tag.isTagged(c); // true

tag.remove(a);
tag.isTagged(a); // false

tag.get('tag1'); // [b, c]
tag.get('tag2'); // [c]
tag.get('blah'); // []

tag.remove(c, 'tag1');
tag.get('tag1'); // [b]
于 2013-09-01T01:27:38.970 回答