15

在 JavaScript (ES5+) 中,我试图实现以下场景:

  1. 一个对象(其中将有许多单独的实例),每个对象都有一个只读属性.size,可以通过直接属性读取从外部读取,但不能从外部设置。
  2. 必须从原型上的某些方法维护/更新该.size属性(并且应该保留在原型上)。
  3. 我的 API 已经由规范定义,因此我无法对其进行修改(我正在为已定义的 ES6 对象开发 polyfill)。
  4. 我主要是想防止人们意外地在脚上开枪,并且实际上并不需要防弹只读(尽管防弹越多越好),所以我愿意妥协一些只要obj.size = 3;不允许直接设置,就可以通过侧门进入物业。

我知道我可以使用在构造函数中声明的私有变量并设置一个 getter 来读取它,但是我必须将需要维护该变量的方法移出原型并在构造函数中声明它们(所以他们可以访问包含变量的闭包)。对于这种特殊情况,我宁愿不将我的方法从原型中移除,所以我正在寻找其他选项可能是什么。

可能还有什么其他想法(即使有一些妥协)?

4

3 回答 3

11
于 2014-05-23T06:38:56.477 回答
4

老实说,我发现为了在 JS 中强制执行真正的隐私需要做出太多牺牲(除非您正在定义一个模块),所以我更喜欢仅依赖命名约定,例如this._myPrivateVariable.

这对任何开发人员来说都是一个明确的指示,他们不应该直接访问或修改这个成员,也不需要牺牲使用原型的好处。

如果您需要将您的size成员作为属性访问,您将别无选择,只能在原型上定义一个 getter。

function MyObj() {
    this._size = 0;
}

MyObj.prototype = {
    constructor: MyObj,

    incrementSize: function () {
        this._size++;
    },

    get size() { return this._size; }
};

var o = new MyObj();

o.size; //0
o.size = 10;
o.size; //0
o.incrementSize();
o.size; //1

我见过的另一种方法是使用模块模式来创建一个privates对象映射,该映射将保存各个实例的私有变量。实例化时,会在实例上分配一个只读私钥,然后使用该密钥从privates对象中设置或检索值。

var MyObj = (function () {
    var privates = {}, key = 0;

    function initPrivateScopeFor(o) {
       Object.defineProperty(o, '_privateKey', { value: key++ });
       privates[o._privateKey] = {};
    }

    function MyObj() {
        initPrivateScopeFor(this);
        privates[this._privateKey].size = 0;
    }

    MyObj.prototype = {
        constructor: MyObj,

        incrementSize: function () {  privates[this._privateKey].size++;  },

        get size() { return privates[this._privateKey].size; }
    };

    return MyObj;

})();

您可能已经注意到,这种模式很有趣,但上述实现存在缺陷,因为即使没有对持有密钥的实例对象的引用,私有变量也永远不会被垃圾回收。

然而,有了 ES6 WeakMap,这个问题就消失了,它甚至简化了设计,因为我们可以使用对象实例作为键,而不是像上面那样使用数字。如果实例被垃圾回收,weakmap 将不会阻止对该对象引用的值的垃圾回收。

于 2014-05-23T01:51:43.387 回答
-1

我最近一直在这样做:

// File-scope tag to keep the setters private.
class PrivateTag {}
const prv = new PrivateTag();

// Convenience helper to set the size field of a Foo instance.
function setSize(foo, size)
{
  Object.getOwnPropertyDiscriptor(foo, 'size').set(size, prv);
}

export default class Foo
{
  constructor()
  {
    let m_size = 0;
    Object.defineProperty(
      this, 'size',
      {
        enumerable: true,
        get: () => { return m_size; },
        set: (newSize, tag = undefined) =>
        {
          // Ignore non-private calls to the setter.
          if (tag instanceof PrivateTag)
          {
            m_size = newSize;
          }
        }
      });
  }

  someFunc()
  {
    // Do some work that changes the size to 1234...
    setSize(this, 1234);
  }      
}

我认为这涵盖了 OP 的所有观点。我没有做过任何性能分析。对于我的用例,正确性更为重要。

想法?

于 2018-08-21T14:08:21.787 回答