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

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

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

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

当前回答

实际上这是可能的。 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);
}

其他回答

是的-你可以创建封装的属性,但它没有与访问修饰符(公共|私有)至少不是与ES6。

下面是一个简单的例子,如何用ES6完成:

用类词创建类

2在构造函数内部使用let OR const保留字声明块作用域变量->因为它们是块作用域,所以不能从外部访问(封装)

为了允许对这些变量进行一些访问控制(setter |getter),你可以在它的构造函数中使用:this.methodName=function(){}语法声明实例方法

"use strict";
    class Something{
        constructor(){
            //private property
            let property="test";
            //private final (immutable) property
            const property2="test2";
            //public getter
            this.getProperty2=function(){
                return property2;
            }
            //public getter
            this.getProperty=function(){
                return property;
            }
            //public setter
            this.setProperty=function(prop){
                property=prop;
            }
        }
    }

现在让我们检查一下:

var s=new Something();
    console.log(typeof s.property);//undefined 
    s.setProperty("another");//set to encapsulated `property`
    console.log(s.getProperty());//get encapsulated `property` value
    console.log(s.getProperty2());//get encapsulated immutable `property2` value

除了给出的答案之外,您还可以使用代理来创建“私有属性”,使代理只对公共代码可用。实例只对构造函数、绑定方法和作为接收者的Proxy本身可用。

这比使用符号和弱映射有一些优势。

符号是可枚举的,可以用代理捕获。 当实例被代理为实例时,WeakMaps失败!== new Proxy(instance)

WeakMap失败。

const map = new WeakMap()

const instance = new SomeClass()
map.set(instance, 'foo')
// somewhere along the way in 3rd party code
const proxy = new Proxy(instance, {})
assert(map.set(instance) === map.get(proxy)) // fail
const proxy2 = new Proxy(proxy, {})
// more headache

使用代理用私有道具验证来装饰实例

getProxy = (instance) => new Proxy(instance, {

    get: (target, name, receiver) => {
        console.log('get', { target, name, receiver })
        if (name[0] === '_') throw new Error('Cannot access private property ' + name)
        return Reflect.get(target, name, receiver) 
    },
    set: (target, name, value, receiver) => {
        console.log('set', { target, name, value, receiver })
        if (name[0] === '_') throw new Error('Cannot set private property ' + name)
        return Reflect.set(target, name, value, receiver) 
    }
    
})


class PublicClass {

    constructor() {
        Object.defineProperty(this, '_privateProp', { enumerable: false, writable: true, configurable: false })
        return getProxy(this) // can be moved out as a decorator
    }

    getPrivatePropFail() {
        return this._privateProp // fail
    }

    getPrivateProp = () => {
        return this._privateProp // ok
    }

    setPrivateProp = (value) => {
        return this._privateProp = value // ok
    }

}


pub = new PublicClass()

try {
    console.log('get pub._privateProp', pub._privateProp)
} catch(e) {
    console.error(e) 
}
try {
    console.log('set pub._privateProp', pub._privateProp = 'you fail')
} catch(e) {
    console.error(e) 
}
pub.setPrivateProp('you ok')
console.log('pub.getPrivateProp()', pub.getPrivateProp())
console.log('pub', Object.keys(pub))

这种方法的优点

私有属性访问验证被装饰在实例上(可选)。 私有属性可以在控制台、调试器和测试环境中检查,属性简单(没有符号或映射) 您可以控制验证和错误处理

的缺点

代理增加了开销和抽象级别 调试将显示包装对象的Proxy() 访问私有道具的方法需要是箭头函数 当无意中暴露实例时,可能会泄漏私有道具。添加一个方法getSelf = () => this

注:

考虑到开销,这种方法可以用于属性封装和调试的清晰度超过开销的场景。例如,当从存储中填充模型时。如。setjson (json)将确保没有私有道具被破坏。

通过使用WeakMap和Proxy来确保“私有”属性不可见,同时允许在每个作用域上使用相同实例访问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)
}

甚至Typescript也做不到。从他们的文档来看:

当成员被标记为private时,就不能从其包含类的外部访问它。例如: 动物类{ 私有名称:字符串; 构造函数(名称:字符串){this.name =名称;} } 新的动物(猫). name;//错误:'name'是private;

但在他们的操场上,这说明:

var Animal = (function () {
    function Animal(theName) {
        this.name = theName;
    }
    return Animal;
}());
console.log(new Animal("Cat").name);

所以他们的“私人”关键字是无效的。

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

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