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

其他回答

这取决于你问谁:-)

最大最小类提案中没有包含私有属性修饰符,这似乎已经进入了当前的草案。

但是,可能会支持私有名称,它允许私有属性—而且它们可能也可以用于类定义中。

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

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

现在大多数浏览器都支持私有类特性。

class Something {
  #property;

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

  #privateMethod() {
    return 'hello world';
  }

  getPrivateMessage() {
      return this.#property;
  }
}

const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> test
console.log(instance.#property); //=> Syntax error

读了前面的答案,我认为这个例子可以总结以上的解决方案

const friend = Symbol('friend');

const ClassName = ((hidden, hiddenShared = 0) => {

    class ClassName {
        constructor(hiddenPropertyValue, prop){
            this[hidden] = hiddenPropertyValue * ++hiddenShared;
            this.prop = prop
        }

        get hidden(){
            console.log('getting hidden');
            return this[hidden];
        }

        set [friend](v){
            console.log('setting hiddenShared');
            hiddenShared = v;
        }

        get counter(){
            console.log('getting hiddenShared');
            return hiddenShared;
        }

        get privileged(){
            console.log('calling privileged method');
            return privileged.bind(this);
        }
    }

    function privileged(value){
        return this[hidden] + value;
    }

    return ClassName;
})(Symbol('hidden'), 0);

const OtherClass = (() => class OtherClass extends ClassName {
    constructor(v){
        super(v, 100);
        this[friend] = this.counter - 1;
    }
})();

更新

现在有可能使真正的私有属性和方法(至少在基于chrome的浏览器现在)。

语法非常简洁

class MyClass {
    #privateProperty = 1
    #privateMethod() { return 2 }
    static #privateStatic = 3
    static #privateStaticMethod(){return 4}
    static get #privateStaticGetter(){return 5}

    // also using is quite straightforward
    method(){
        return (
            this.#privateMethod() +
            this.#privateProperty +
            MyClass.#privateStatic +
            MyClass.#privateStaticMethod() +
            MyClass.#privateStaticGetter
        )
    }
}

new MyClass().method()
// returns 15

注意,对于检索静态引用,不能使用this.constructor。#private,因为它会终止它的子类。你必须使用一个对适当类的引用来检索它的静态私有引用(只能在该类的方法中使用),即MyClass.#private。

更新:一个语法更好的提案正在酝酿中。欢迎投稿。


是的,对于对象的作用域访问,ES6引入了符号。

符号是唯一的,你不能从外部访问一个,除非通过反射(就像Java/ c#中的私有),但任何有权访问内部符号的人都可以使用它进行键访问:

var property = Symbol();
class Something {
    constructor(){
        this[property] = "test";
    }
}

var instance = new Something();

console.log(instance.property); //=> undefined, can only access with access to the Symbol