是否可以在ES6类中创建私有属性?

举个例子。 如何阻止访问instance.property?

class Something {
  constructor(){
    this.property = "test";
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"

当前回答

我现在听到的建议是使用WeakMaps来保存私人数据,以供其他看客参考。

下面是一个更清晰、有效的例子:

function storePrivateProperties(a, b, c, d) {
  let privateData = new WeakMap;
  // unique object as key, weak map can only accept object as key, when key is no longer referened, garbage collector claims the key-value 
  let keyA = {}, keyB = {}, keyC = {}, keyD = {};

  privateData.set(keyA, a);
  privateData.set(keyB, b);
  privateData.set(keyC, c);
  privateData.set(keyD, d);

  return {
    logPrivateKey(key) {
      switch(key) {
      case "a":
        console.log(privateData.get(keyA));
        break;
      case "b":
        console.log(privateData.get(keyB));
        break;
      case "c":
        console.log(privateData.get(keyC));
        break;
      case "d":
        console.log(privateData.set(keyD));
        break;
      default:
        console.log(`There is no value for ${key}`)
      }
    }
  }
}

其他回答

我认为Benjamin的答案在大多数情况下可能是最好的,直到语言本身支持显式私有变量。

但是,如果出于某种原因需要阻止使用object . getownpropertysymbols()进行访问,我考虑使用的一种方法是附加一个惟一的、不可配置的、不可枚举的、不可写的属性,可以将其用作构造中的每个对象的属性标识符(例如惟一的Symbol,如果您还没有其他一些惟一的属性,如id)。然后使用该标识符保存每个对象的“私有”变量的映射。

const privateVars = {};

class Something {
    constructor(){
        Object.defineProperty(this, '_sym', {
            configurable: false,
            enumerable: false,
            writable: false,
            value: Symbol()
        });

        var myPrivateVars = {
            privateProperty: "I'm hidden"
        };

        privateVars[this._sym] = myPrivateVars;

        this.property = "I'm public";
    }

    getPrivateProperty() {
        return privateVars[this._sym].privateProperty;
    }

    // A clean up method of some kind is necessary since the
    // variables won't be cleaned up from memory automatically
    // when the object is garbage collected
    destroy() {
        delete privateVars[this._sym];
    }
}

var instance = new Something();
console.log(instance.property); //=> "I'm public"
console.log(instance.privateProperty); //=> undefined
console.log(instance.getPrivateProperty()); //=> "I'm hidden"

与使用WeakMap相比,这种方法的潜在优势是,如果需要考虑性能,则访问时间更快。

在JS中获得真正隐私的唯一方法是通过作用域,因此不可能有一个属性是this的成员,只能在组件内部访问。在ES6中存储真正私有数据的最佳方法是使用WeakMap。

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    privateProp1.set(this, "I am Private1");
    privateProp2.set(this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(privateProp1.get(this), privateProp2.get(this))
    };        
  }

  printPrivate() {
    console.log(privateProp1.get(this));
  }
}

显然,这可能是一个缓慢的,肯定是丑陋的,但它确实提供了隐私。

请记住,即使这样也不是完美的,因为Javascript是动态的。有人仍然可以这样做

var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
    // Store 'this', 'key', and 'value'
    return oldSet.call(this, key, value);
};

为了在值被存储时捕获它们,所以如果你想格外小心的话,你需要捕获一个对.set和.get的本地引用来显式使用,而不是依赖于可覆盖的原型。

const {set: WMSet, get: WMGet} = WeakMap.prototype;

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    WMSet.call(privateProp1, this, "I am Private1");
    WMSet.call(privateProp2, this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(WMGet.call(privateProp1, this), WMGet.call(privateProp2, this))
    };        
  }

  printPrivate() {
    console.log(WMGet.call(privateProp1, this));
  }
}

哦,这么多奇异的解决方案!我通常不关心隐私,所以我使用“伪隐私”,正如这里所说的。但如果确实关心(如果有一些特殊的要求),我会在这个例子中使用:

class jobImpl{
  // public
  constructor(name){
    this.name = name;
  }
  // public
  do(time){
    console.log(`${this.name} started at ${time}`);
    this.prepare();
    this.execute();
  }
  //public
  stop(time){
    this.finish();
    console.log(`${this.name} finished at ${time}`);
  }
  // private
  prepare(){ console.log('prepare..'); }
  // private
  execute(){ console.log('execute..'); }
  // private
  finish(){ console.log('finish..'); }
}

function Job(name){
  var impl = new jobImpl(name);
  return {
    do: time => impl.do(time),
    stop: time => impl.stop(time)
  };
}

// Test:
// create class "Job"
var j = new Job("Digging a ditch");
// call public members..
j.do("08:00am");
j.stop("06:00pm");

// try to call private members or fields..
console.log(j.name); // undefined
j.execute(); // error

函数(构造函数)的另一种可能实现:

function Job(name){
  var impl = new jobImpl(name);
  this.do = time => impl.do(time),
  this.stop = time => impl.stop(time)
}

答案是“不”。但是你可以像这样创建对属性的私有访问:

使用模块。模块中的所有内容都是私有的,除非使用export关键字将其设为公有。 在模块内部,使用函数闭包:http://www.kirupa.com/html5/closures_in_javascript.htm

(可以使用符号来确保隐私的建议在ES6规范的早期版本中是正确的,但现在不再是这样:https://mail.mozilla.org/pipermail/es-discuss/2014-January/035604.html和https://stackoverflow.com/a/22280202/1282216。有关符号和隐私的详细讨论,请参阅:https://curiosity-driven.org/private-properties-in-javascript)

使用WeakMap可以在类中拥有私有方法。

根据MDN网络文档:

WeakMap对象是键/值对的集合,其中键仅为对象,值可以是任意值。 键中的对象引用是弱保存的,这意味着如果不再有对该对象的其他引用,则它们是垃圾收集(GC)的目标。

这是一个创建带有私有成员_items的Queue数据结构的例子,该成员_items包含一个数组。

const _items = new WeakMap();

class Queue {    
    constructor() {
        _items.set(this, []);
    }

    enqueue( item) {
        _items.get(this).push(item);
    }    

    get count() {
        return _items.get(this).length;        
    }

    peek() {
        const anArray = _items.get(this);
        if( anArray.length == 0)
            throw new Error('There are no items in array!');

        if( anArray.length > 0)
            return anArray[0];
    }

    dequeue() {        
        const anArray = _items.get(this);
        if( anArray.length == 0)
            throw new Error('There are no items in array!');

        if( anArray.length > 0)
            return anArray.splice(0, 1)[0];
    }    
}

使用的例子:

const c = new Queue();
c.enqueue("one");
c.enqueue("two");
c.enqueue("three");
c.enqueue("four");
c.enqueue("five");
console.log(c);

私有成员_items是隐藏的,不能在Queue对象的属性或方法中看到:

然而,Queue对象中的私有成员_items可以通过以下方式访问:

const anArray = _items.get(this);