8

我想取一个对象并从中删除一些方法。

即我在内部有一个带有getter/setter 的对象,我想让外部用户访问它。我不希望他们能够访问 setter 函数。

我不想通过从中删除方法来更改原始对象引用,而是创建一个指向同一对象但方法较少的新对象引用。

  • 我该怎么做呢?
  • 这是设计模式吗?
  • 这类问题是否有众所周知的解决方案?

我有这个功能的实现

var readOnly = function(obj, publicData) {
    // create a new object so that obj isn't effected
    var object = new obj.constructor;
    // remove all its public keys
    _.each(object, function(val, key) {
        delete object[key];    
    });
    // bind all references to obj
    _.bindAll(obj);
    // for each public method give access to it
    _.each(publicData, function(val) {
        object[val] = obj[val];    
    });
    return object;
};

活的例子_.each _.bindAll

出于所有预期目的,返回的对象应该与原始对象相同,只是某些方法不再存在。内部this参考不应中断任何功能。原型链不应中断。

  • 这种功能的直观名称是什么?
  • 我当前的实现是否有任何我应该注意的陷阱?
4

9 回答 9

5

您应该做的是使用仅具有您想要保留的方法的外观模式和委托模式将这些方法委托给原始对象的实例。使用 Javascript 提供的丰富反射,您应该能够相当容易地以编程方式生成这些 Facades。

于 2011-02-23T17:38:06.217 回答
2

您的实施中至少存在一个问题是对obj.constructor. constructor众所周知,该属性不是只读的,而且很容易搞砸。考虑以下模式,这是在 Javascript 中定义类的一种非常常见的方式:

function Foo() {};
Foo.prototype = {
    myProperty: 1,
    myFunction: function() {
        return 2;
    }
};
// make an instance
var foo = new Foo();
foo instanceof Foo; // true
foo.constructor == Foo;  // false! The constructor is now Object
(new foo.constructor()) instanceof Foo; // false

我认为这样做的方法是以您的obj实例为原型创建一个新类。然后,您可以通过在新类的对象上添加空键来阻止对旧功能的访问:

function getRestricted(obj, publicProperties) {
    function RestrictedObject() {};
    RestrictedObject.prototype = obj;
    var ro = new RestrictedObject();
    // add undefined keys to block access
    for (var key in obj) {
        // note: use _.indexOf instead -- this was just easier to test
        if (publicProperties.indexOf(key) < 0) {
            ro[key] = null;
        } else {
            // again, use _.isFunction if you prefer
            if (typeof obj[key]=='function') {
                (function(key) {
                    // wrap functions to use original scope
                    ro[key] = function() {
                        // basically the same as _.bind
                        return obj[key].apply(obj, arguments);
                    }
                })(key);
            }
        }
    }
    return ro;
}

function Foo() {
    var a=0;
    this.getA = function() {
        this.setA(a+1);
        return a;
    };
    this.setA = function(newa) {
        a = newa;
    }
};

// make an instance
var foo = new Foo();
foo.setA(1);
foo.getA(); // 2

// make a restricted instance
var restrictedFoo = getRestricted(foo, ['getA']);
restrictedFoo.getA(); // 3
restrictedFoo instanceof Foo; // true
try {
    restrictedFoo.setA(2); // TypeError: Property 'setA' is not a function
} catch(e) {
    "not a function";
}
// one bump here:
"setA" in restrictedFoo; // true - just set to undefined

// foo is unaffected
foo.setA(4);
foo.getA(); // 5

(这部分基于 Crockford 的幂构造函数,在此处讨论。)


编辑:我更新了上面的代码以解决您的评论。它现在看起来有点类似于您的实现,但它更简单并且避免了这个constructor问题。如您所见,公共函数中的引用现在指的是旧对象。

于 2011-02-25T05:31:36.453 回答
1

如果你想要instanceof作品,我们需要使用继承,比如@nrabinowitz 的解决方案。在该解决方案中,将不需要的方法隐藏起来,并将键设置为 null,并且用户可以访问这些键,因此它们可能会被用户重置。我们可以通过将这些键隐藏在中间对象中来防止这种情况,并且因为它是从中间类实例化的,所以继承不会中断。

function restrict(original, whitelist) {
    /* create intermediate class and instantiate */
    var intermediateClass = function() {};
    intermediateClass.prototype = original;
    var intermediateObject = new intermediateClass();

    /* create restricted class and fix constructor reference after prototype replacement */
    var restrictedClass = function() {};
    restrictedClass.prototype = intermediateObject;
    restrictedClass.prototype.constructor = original.constructor;
    if (restrictedClass.prototype.constructor == Object.prototype.constructor) {
        restrictedClass.prototype.constructor = restrictedClass;
    }

    for (var key in original) {
        var found = false;
        for (var i = 0; i < whitelist.length; i++) {
            if (key == whitelist[i]) {
                if (original[key] instanceof Function) {
                    /* bind intermediate method to original method */
                    (function(key) {
                        intermediateObject[key] = function() {
                            return original[key].apply(original, arguments);
                        }
                    })(key);
                }
                found = true;
                break;
            }
        }
        if (!found) {
            /* black out key not in the whitelist */
            intermediateObject[key] = undefined;
        }
    }

    return new restrictedClass();
}

在下面的示例中,ij表示实现成员值的两种方式。一个是闭包中的私有成员,另一个是类的公共成员。

var originalClass = function() {
    var i = 0;
    this.j = 0;

    this.getI = function() {
        return i;
    };
    this.setI = function(val) {
        i = val;
    };
}
originalClass.prototype.increaseI = function() {
    this.setI(this.getI() + 1);
};
originalClass.prototype.decreaseI = function() {
    this.setI(this.getI() - 1);
};
originalClass.prototype.getJ = function() {
    return this.j;
};
originalClass.prototype.setJ = function(val) {
    this.j = val;
};
originalClass.prototype.increaseJ = function() {
    this.setJ(this.getJ() + 1);
};
originalClass.prototype.decreaseJ = function() {
    this.setJ(this.getJ() - 1);
};

var originalObject = new originalClass();
var restrictedObject = restrict(originalObject, ["getI", "increaseI", "getJ", "increaseJ"]);

restrictedObject.increaseI();
restrictedObject.increaseJ();
console.log(originalObject.getI()); // 1
console.log(originalObject.getJ()); // 1
console.log(restrictedObject instanceof originalClass); // true

如您所见,所有 setter 和 reduce 方法都隐藏在受限对象中。用户只能使用 getter 或增加iand的值j

于 2011-02-26T06:45:52.397 回答
0

如果您只想将对象中的某些方法公开并保持其余方法为私有,则可以执行以下操作(在下面的示例中,privateMethod2 永远不会在返回给用户的新“只读”对象中显示):

function MyObject() {

  // Private member
  var name = "Bob"; 

  // Private methods
  function setName(n) { name = n; }
  function privateMethod2() { ... }
  function privateMethod3() { ... }

  // Expose certain methods in a new object
  this.readOnly() { 
    return {
      publicMethod1: setName,
      publicMethod2: privateMethod3
    }; 
  }

}
于 2011-02-20T17:13:49.077 回答
0

您正在寻找的是 JavaScript 中的模块模式。它与 Gobhi 所描述的非常接近,但通常它是一个自执行函数。

详细信息可以在这里找到 :

http://yuiblog.com/blog/2007/06/12/module-pattern/

和 :

http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth

于 2011-02-23T17:28:09.413 回答
0

您正在选择非常复杂的方法。首先提供更多详细信息:

  • 是否真的有必要扩展原始对象原型(也就是在运行时更改对象原型多少次)?
  • 你要创建多少个不同的对象?我所说的不同是指:从相同的对象类型创建,但使用不同的公共方法。
  • 您打算锁定多少种对象类型?
  • 您计划多久更新一次代码?
  • 您是否使用任何类型的压缩器/编译器?

对评论 1 的回应

  • 如果您锁定所有对象,那么处理性能问题(尤其是 IE)只是时间问题。
  • 迄今为止,您有 2-3 个要锁定的对象类型。在委派模式中准备这 2-3 个对象不是更容易吗?请记住,您必须为灵活性付出代价;
  • 尝试在高级模式下使用Closure 编译器。当您利用此工具时,您将学习如何混淆对象属性并离开公共界面。再加上每次代码更改和重新编译都会产生新的隐藏变量/函数名称,所以祝你好运,试图猜测一个是 setA 或 getA 函数。
  • 最后但并非最不重要的。尽量让您的公共界面保持整洁和小巧。这意味着:用匿名函数包装所有代码,并尽可能少地公开对象/方法/属性。

关于 Closure 编译器混淆工作的更多信息。

  1. 编译将所有对象属性重命名为:aa、ab、ac 等;
  2. 您公开公共方法/属性:a.prototype.getA = a.prototype.aa; (在内部您使用 a.prototype.aa 方法。这意味着可以用任何值替换公共方法 - 这对您的代码没有影响);
  3. 最后你公开对象:window['foo'] = new a;

Google 使用以下方法(GMaps、GMail 等)。

于 2011-02-24T06:24:47.000 回答
0

我认为@nrabinowitz 的getRestricted链接)功能几乎就是您要寻找的答案。

我不是 GOF 模式应用到 JavaScript 的忠实粉丝(这本身就有一个完整的讨论)。但这对我来说闻起来像装饰器,因为我们正在更改某些对象的运行时行为-但反过来-如果您愿意,可以使用 De-Decorator :)

于 2011-02-25T07:28:40.057 回答
-1

也许:

var yourfunction = function() {
   var that = {};

   var constructor = function() {
   };

   //private methods
   var privateMethod = function() {
   };

   constructor();

   //public methods   
   that.publicMethod = function() {
   };

   return that;
}
于 2011-02-23T10:20:29.297 回答
-1

我会说与您的情况非常匹配的模式是Proxy

更新:正如评论所示,代理应支持与真实对象相同的接口,因此与问题中所述的问题不匹配。在关于 Proxy 的维基百科文章的底部,我找到了一篇有趣文章的链接,该文章比较了Proxy、Adapter 和 Facade模式。

于 2011-02-23T14:41:01.863 回答