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

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

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

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

当前回答

这段代码演示了私有和公共、静态和非静态、实例级和类级、变量、方法和属性。

https://codesandbox.io/s/class-demo-837bj

class Animal { static count = 0 // class static public static #ClassPriVar = 3 // class static private constructor(kind) { this.kind = kind // instance public property Animal.count++ let InstancePriVar = 'InstancePriVar: ' + kind // instance private constructor-var log(InstancePriVar) Animal.#ClassPriVar += 3 this.adhoc = 'adhoc' // instance public property w/out constructor- parameter } #PawCount = 4 // instance private var set Paws(newPawCount) { // instance public prop this.#PawCount = newPawCount } get Paws() { // instance public prop return this.#PawCount } get GetPriVar() { // instance public prop return Animal.#ClassPriVar } static get GetPriVarStat() { // class public prop return Animal.#ClassPriVar } PrintKind() { // instance public method log('kind: ' + this.kind) } ReturnKind() { // instance public function return this.kind } /* May be unsupported get #PrivMeth(){ // instance private prop return Animal.#ClassPriVar + ' Private Method' } static get #PrivMeth(){ // class private prop return Animal.#ClassPriVar + ' Private Method' } */ } function log(str) { console.log(str) } // TESTING log(Animal.count) // static, avail w/out instance log(Animal.GetPriVarStat) // static, avail w/out instance let A = new Animal('Cat') log(Animal.count + ': ' + A.kind) log(A.GetPriVar) A.PrintKind() A.Paws = 6 log('Paws: ' + A.Paws) log('ReturnKind: ' + A.ReturnKind()) log(A.adhoc) let B = new Animal('Dog') log(Animal.count + ': ' + B.kind) log(B.GetPriVar) log(A.GetPriVar) // returns same as B.GetPriVar. Acts like a class-level property, but called like an instance-level property. It's cuz non-stat fx requires instance. log('class: ' + Animal.GetPriVarStat) // undefined log('instance: ' + B.GetPriVarStat) // static class fx log(Animal.GetPriVar) // non-stat instance fx log(A.InstancePriVar) // private log(Animal.InstancePriVar) // private instance var log('PawCount: ' + A.PawCount) // private. Use getter /* log('PawCount: ' + A.#PawCount) // private. Use getter log('PawCount: ' + Animal.#PawCount) // Instance and private. Use getter */

其他回答

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

实际上这是可能的。 1. 首先,创建类,并在构造函数中返回被调用的_public函数。 2. 在被调用的_public函数中传递this引用(以获得对所有私有方法和道具的访问),以及构造函数的所有参数(将在new Names()中传递) 3.在_public函数作用域中,还有一个Names类,它可以访问私有Names类的this (_this)引用

class Names {
  constructor() {
    this.privateProperty = 'John';
    return _public(this, arguments);
  }
  privateMethod() { }
}

const names = new Names(1,2,3);
console.log(names.somePublicMethod); //[Function]
console.log(names.publicProperty); //'Jasmine'
console.log(names.privateMethod); //undefined
console.log(names.privateProperty); //undefind

function _public(_this, _arguments) {
  class Names {
    constructor() {
      this.publicProperty = 'Jasmine';
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

    somePublicMethod() {
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

  }
  return new Names(..._arguments);
}

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

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

我已经开发了一个模块,帮助您使用的访问限制 JavaScript类,叫做Capsulable。(私有和受保护的静态)

如果您感兴趣,请查看下面的软件包。 https://github.com/hmmhmmhm/capsulable

const Capsulable = require('capsulable')
const Field = Capsulable()

class A {
    constructor(_field){
        // Configure data fields.
        Field(this, _field)

        // The code below provides access to
        // the data fields when creating
        // functions within the class.
        Field(this).private
        Field(this).protected
        Field(this).protectedStatic
    }
}

module.exports = A

使用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);