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

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

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

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

当前回答

是的,在名称前面加上#,并将其包含在类定义中,而不仅仅是构造函数中。

MDN文档

真正的私人财产终于在ES2022中加入。截至2023-01-01,私有属性(字段和方法)在所有主要浏览器中已经支持至少一年了,但5-10%的用户仍然使用旧的浏览器[我可以使用吗]。

例子:

class Person {
  #age

  constructor(name) {
    this.name = name; // this is public
    this.#age = 20; // this is private
  }

  greet() {
    // here we can access both name and age
    console.log(`name: ${this.name}, age: ${this.#age}`);
  }
}

let joe = new Person('Joe');
joe.greet();

// here we can access name but not age

以下是在es2022之前的环境中保持属性私有的方法,其中有各种权衡。

作用域的变量

这里的方法是使用构造函数的私有作用域来存储私有数据。对于能够访问这些私有数据的方法,它们也必须在构造函数中创建,这意味着您将在每个实例中重新创建它们。这是一个性能和内存损失,但它可能是可以接受的。对于不需要访问私有数据的方法,可以通过以正常方式声明它们来避免这种惩罚。

例子:

class Person {
  constructor(name) {
    let age = 20; // this is private
    this.name = name; // this is public

    this.greet = () => {
      // here we can access both name and age
      console.log(`name: ${this.name}, age: ${age}`);
    };
  }

  anotherMethod() {
    // here we can access name but not age
  }
}

let joe = new Person('Joe');
joe.greet();

// here we can access name but not age

作用域WeakMap

A WeakMap can be used to improve the performance of the above approach, in exchange for even more clutter. WeakMaps associate data with Objects (here, class instances) in such a way that it can only be accessed using that WeakMap. So, we use the scoped variables method to create a private WeakMap, then use that WeakMap to retrieve private data associated with this. This is faster than the scoped variables method because all your instances can share a single WeakMap, so you don't need to recreate methods just to make them access their own WeakMaps.

例子:

let Person = (function () {
  let privateProps = new WeakMap();

  return class Person {
    constructor(name) {
      this.name = name; // this is public
      privateProps.set(this, {age: 20}); // this is private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${privateProps.get(this).age}`);
    }
  };
})();

let joe = new Person('Joe');
joe.greet();

// here we can access name but not age

这个例子使用一个带有对象键的WeakMap来为多个私有属性使用一个WeakMap;你也可以像privateAge一样使用多个弱地图。set(this, 20),或者写一个小包装器并以另一种方式使用它,如privateProps。Set (this, 'age', 0)。

这种方法的私密性理论上可以通过篡改全局WeakMap对象来破坏。也就是说,所有JavaScript都可能被损坏的全局变量破坏。

(这个方法也可以用Map,但WeakMap更好,因为Map会造成内存泄漏,除非你非常小心,在这个目的上,两者并没有什么不同。)

半回答:范围符号

Symbol是一种基本值类型,可以作为属性名而不是字符串。您可以使用scoped变量方法来创建一个私有Symbol,然后将私有数据存储在这个[mySymbol]中。

使用Object可以破坏该方法的私密性。getOwnPropertySymbols,但是这样做有点尴尬。

例子:

let Person = (() => {
  let ageKey = Symbol();

  return class Person {
    constructor(name) {
      this.name = name; // this is public
      this[ageKey] = 20; // this is intended to be private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${this[ageKey]}`);
    }
  }
})();

let joe = new Person('Joe');
joe.greet();

// Here we can access joe's name and, with a little effort, age. We can’t
// access ageKey directly, but we can obtain it by listing all Symbol
// properties on `joe` with `Object.getOwnPropertySymbols(joe)`.

注意,使用Object.defineProperty使属性不可枚举并不会阻止它被包含在Object.getOwnPropertySymbols中。

Half-Answer:强调

旧的约定是只使用带有下划线前缀的公共属性。这并没有使它保持隐私,但它确实很好地与读者沟通,他们应该将其视为隐私,这通常可以完成工作。作为交换,我们得到了一种比其他变通方法更容易阅读、更容易输入和更快的方法。

例子:

class Person {
  constructor(name) {
    this.name = name; // this is public
    this._age = 20; // this is intended to be private
  }

  greet() {
    // Here we can access both name and age
    console.log(`name: ${this.name}, age: ${this._age}`);
  }
}

let joe = new Person('Joe');
joe.greet();

// Here we can access both joe's name and age. But we know we aren't
// supposed to access his age, which just might stop us.

总结

ES2022:很棒,但还不是所有访问者都支持 作用域变量:私有、较慢、笨拙 作用域弱映射:可攻击,尴尬 范围符号:可枚举和可破解,有点尴尬 强调:只是要求隐私,没有其他缺点

其他回答

我认为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相比,这种方法的潜在优势是,如果需要考虑性能,则访问时间更快。

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

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)
}

Yes totally can, and pretty easily too. This is done by exposing your private variables and functions by returning the prototype object graph in the constructor. This is nothing new, but take a bit of js foo to understand the elegance of it. This way does not use global scoped, or weakmaps. It is a form of reflection built into the language. Depending on how you leverage this; one can either force an exception which interrupts the call stack, or bury the exception as an undefined. This is demonstarted below, and can read more about these features here

class Clazz { constructor() { var _level = 1 function _private(x) { return _level * x; } return { level: _level, public: this.private, public2: function(x) { return _private(x); }, public3: function(x) { return _private(x) * this.public(x); }, }; } private(x) { return x * x; } } var clazz = new Clazz(); console.log(clazz._level); //undefined console.log(clazz._private); // undefined console.log(clazz.level); // 1 console.log(clazz.public(1)); //1 console.log(clazz.public2(2)); //2 console.log(clazz.public3(3)); //27 console.log(clazz.private(0)); //error

你可以试试这个https://www.npmjs.com/package/private-members

这个包将按实例保存成员。

const pvt = require('private-members');
const _ = pvt();

let Exemplo = (function () {    
    function Exemplo() {
        _(this).msg = "Minha Mensagem";
    }

    _().mensagem = function() {
        return _(this).msg;
    }

    Exemplo.prototype.showMsg = function () {
        let msg = _(this).mensagem();
        console.log(msg);
    };

    return Exemplo;
})();

module.exports = Exemplo;
class Something {
  constructor(){
    var _property = "test";
    Object.defineProperty(this, "property", {
        get: function(){ return _property}
    });
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"
instance.property = "can read from outside, but can't write";
console.log(instance.property); //=> "test"