521

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

这是一个例子。如何防止访问instance.property

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

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

41 回答 41

312

简短的回答,不,没有对 ES6 类的私有属性的原生支持。

但是您可以通过不将新属性附加到对象来模仿这种行为,而是将它们保留在类构造函数中,并使用 getter 和 setter 来访问隐藏的属性。请注意,getter 和 setter 会在类的每个新实例上重新定义。

ES6

class Person {
    constructor(name) {
        var _name = name
        this.setName = function(name) { _name = name; }
        this.getName = function() { return _name; }
    }
}

ES5

function Person(name) {
    var _name = name
    this.setName = function(name) { _name = name; }
    this.getName = function() { return _name; }
}
于 2015-01-27T07:52:22.090 回答
279

私有类功能第 3 阶段提案中。所有主要浏览器都支持其大部分功能。

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
于 2018-09-08T18:02:04.297 回答
222

要扩展@loganfsmyth 的答案:

JavaScript 中唯一真正私有的数据仍然是作用域变量。在内部访问属性的方式与公共属性相同,您不能拥有私有属性,但您可以使用作用域变量来存储私有数据。

作用域变量

这里的做法是使用构造函数的作用域,也就是私有的,来存储私有数据。为了让方法能够访问这些私有数据,它们也必须在构造函数中创建,这意味着您正在使用每个实例重新创建它们。这是性能和内存损失,但有些人认为这种损失是可以接受的。通过像往常一样将它们添加到原型中,可以避免不需要访问私有数据的方法的惩罚。

例子:

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

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

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

// here we can access name but not age

作用域弱图

WeakMap 可用于避免先前方法的性能和内存损失。WeakMaps 将数据与对象(此处为实例)相关联,使得数据只能使用该 WeakMap 进行访问。因此,我们使用作用域变量方法创建私有 WeakMap,然后使用该 WeakMap 检索与this. 这比作用域变量方法更快,因为您的所有实例都可以共享一个 WeakMap,因此您不需要重新创建方法来让它们访问自己的 WeakMap。

例子:

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

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

  return Person;
})();

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

// here we can access joe's name but not age

这个例子使用一个 Object 来为多个私有属性使用一个 WeakMap;你也可以使用多个 WeakMap 并age.set(this, 20)privateProps.set(this, 'age', 0).

这种方法的隐私理论上可以通过篡改全局WeakMap对象来破坏。也就是说,所有 JavaScript 都可以被损坏的全局变量破坏。我们的代码已经建立在没有发生这种情况的假设之上。

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

半答案:作用域符号

Symbol 是一种可以用作属性名称的原始值类型。您可以使用作用域变量方法创建私有符号,然后将私有数据存储在this[mySymbol].

使用 可以破坏此方法的隐私Object.getOwnPropertySymbols,但这样做有些尴尬。

例子:

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

  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]}`);
    }
  }

  return Person;
})();

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

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

半答案:下划线

旧的默认值,只需使用带有下划线前缀的公共属性。尽管在任何方面都不是私有财产,但这种约定非常普遍,它很好地传达了读者应该将财产视为私有的,这通常可以完成工作。作为这种失误的交换,我们得到了一种更容易阅读、更容易输入和更快的方法。

例子:

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.

结论

截至 ES2017,仍然没有完美的方法来处理私有属性。各种方法各有利弊。作用域变量是真正私有的;scoped WeakMaps 是非常私有的,比 scoped variables 更实用;scoped Symbols 具有合理的私密性和实用性;下划线通常足够私密且非常实用。

于 2015-11-04T22:52:48.323 回答
123

更新:一个语法更好的提案即将推出。欢迎投稿。


是的,有 - 对于对象中的范围访问 - ES6 引入了Symbols

符号是唯一的,除了反射(如 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
于 2014-03-03T23:51:04.670 回答
35

答案是不”。但是您可以创建对属性的私有访问,如下所示:

(在早期版本的 ES6 规范中,Symbols 可用于确保隐私的建议是正确的,但不再是这种情况:https://mail.mozilla.org/pipermail/es-discuss/2014-January/035604。 htmlhttps://stackoverflow.com/a/22280202/1282216。有关符号和隐私的更长时间的讨论,请参阅:https ://curiosity-driven.org/private-properties-in-javascript )

于 2014-03-19T14:43:36.720 回答
30

在 JS 中获得真正隐私的唯一方法是通过作用域,因此没有办法让作为其成员的属性this只能在组件内部访问。在 ES6 中存储真正私有数据的最佳方式是使用 Wea​​kMap。

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));
  }
}
于 2015-07-21T23:54:30.760 回答
22

为了将来其他旁观者的参考,我现在听说建议是使用Wea​​kMaps来保存私人数据。

这是一个更清晰的工作示例:

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}`)
      }
    }
  }
}
于 2015-01-14T00:09:39.737 回答
12

取决于你问谁:-)

似乎已将其纳入当前草案的最大最小类提案中不包含任何private属性修饰符。

但是,可能支持 私有名称,这确实允许私有属性——它们也可能用于类定义中。

于 2014-03-03T22:20:11.370 回答
10

使用 ES6 模块(最初由 @d13 提出)对我来说效果很好。它不能完美地模仿私有属性,但至少你可以确信应该是私有的属性不会泄漏到你的类之外。这是一个例子:

东西.js

let _message = null;
const _greet = name => {
  console.log('Hello ' + name);
};

export default class Something {
  constructor(message) {
    _message = message;
  }

  say() {
    console.log(_message);
    _greet('Bob');
  }
};

然后消费代码看起来像这样:

import Something from './something.js';

const something = new Something('Sunny day!');
something.say();
something._message; // undefined
something._greet(); // exception

更新(重要):

正如@DanyalAytekin 在评论中概述的那样,这些私有属性是静态的,因此在范围内是全局的。它们在使用 Singleton 时会很好地工作,但必须注意 Transient 对象。扩展上面的例子:

import Something from './something.js';
import Something2 from './something.js';

const a = new Something('a');
a.say(); // a

const b = new Something('b');
b.say(); // b

const c = new Something2('c');
c.say(); // c

a.say(); // c
b.say(); // c
c.say(); // c
于 2016-03-14T13:10:36.780 回答
9

是的 - 您可以创建封装的属性,但至少没有使用 ES6 访问修饰符(public|private)。

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

1 使用类词创建类

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

3 要允许对这些变量进行一些访问控制(setters|getters),您可以在其构造函数中声明实例方法,使用: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
于 2016-02-08T15:28:17.663 回答
9

完成@d13 以及@johnny-oshika 和@DanyalAytekin 的评论:

我猜在@johnny-oshika 提供的示例中,我们可以使用普通函数而不是箭头函数,然后将.bind它们与当前对象加上一个_privates对象作为咖喱参数:

东西.js

function _greet(_privates) {
  return 'Hello ' + _privates.message;
}

function _updateMessage(_privates, newMessage) {
  _privates.message = newMessage;
}

export default class Something {
  constructor(message) {
    const _privates = {
      message
    };

    this.say = _greet.bind(this, _privates);
    this.updateMessage = _updateMessage.bind(this, _privates);
  }
}

main.js

import Something from './something.js';

const something = new Something('Sunny day!');

const message1 = something.say();
something.updateMessage('Cloudy day!');
const message2 = something.say();

console.log(message1 === 'Hello Sunny day!');  // true
console.log(message2 === 'Hello Cloudy day!');  // true

// the followings are not public
console.log(something._greet === undefined);  // true
console.log(something._privates === undefined);  // true
console.log(something._updateMessage === undefined);  // true

// another instance which doesn't share the _privates
const something2 = new Something('another Sunny day!');

const message3 = something2.say();

console.log(message3 === 'Hello another Sunny day!'); // true

我能想到的好处:

  • 我们可以拥有私有方法(只要我们没有引用 _greet,就可以像私有方法一样行事)_updateMessageexport
  • 尽管它们不在原型上,但上述方法将节省内存,因为实例在类外部创建一次(而不是在构造函数中定义它们)
  • 我们不会泄漏任何全局变量,因为我们在模块中
  • 我们也可以使用绑定_privates对象来拥有私有属性

我能想到的一些缺点:

可以在此处找到运行片段:http ://www.webpackbin.com/NJgI5J8lZ

于 2016-04-24T17:54:11.363 回答
7

“私人”的不同方法

我决定采用一种更实用的方法,如果你的 IDE 支持 JSDoc(例如,Webstorm),它就可以很好地解决这个问题,而不是反对 ES6 中当前不可用的私有可见性这一事实。这个想法是使用@private标签。就开发而言,IDE 将阻止您从其类之外访问任何私有成员。对我来说效果很好,它对于隐藏内部方法非常有用,因此自动完成功能向我展示了该类真正想要公开的内容。这是一个例子:

自动完成仅显示公共内容

于 2017-10-01T20:27:25.247 回答
7

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

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

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

function Job(name){
  var impl = new jobImpl(name);
  this.do = time => impl.do(time),
  this.stop = time => impl.stop(time)
}
于 2018-10-28T13:56:31.767 回答
6

弱地图

  • IE11 支持(不支持符号)
  • 硬私有(使用符号的道具是软私有的,因为Object.getOwnPropertySymbols
  • 看起来很干净(不像闭包需要构造函数中的所有道具和方法)

首先,定义一个包装WeakMap的函数:

function Private() {
  const map = new WeakMap();
  return obj => {
    let props = map.get(obj);
    if (!props) {
      props = {};
      map.set(obj, props);
    }
    return props;
  };
}

然后,在你的类之外构造一个引用:

const p = new Private();

class Person {
  constructor(name, age) {
    this.name = name;
    p(this).age = age; // it's easy to set a private variable
  }

  getAge() {
    return p(this).age; // and get a private variable
  }
}

注意: IE11 不支持class,但在示例中看起来更简洁。

于 2017-08-03T23:42:34.223 回答
6

我在寻找“课程私有数据”的最佳实践时遇到了这篇文章。有人提到,一些模式会出现性能问题。

我根据在线书籍“Exploring ES6”中的 4 个主要模式整理了一些 jsperf 测试:

http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes

测试可以在这里找到:

https://jsperf.com/private-data-for-classes

在 Chrome 63.0.3239 / Mac OS X 10.11.6 中,表现最好的模式是“通过构造函数环境的私有数据”和“通过命名约定的私有数据”。对我来说,Safari 在 WeakMap 上的表现不错,但 Chrome 就不太好。

我不知道对内存的影响,但是一些人警告过的“构造函数环境”的模式将是一个性能问题,它的性能非常好。

4种基本模式是:

通过构造函数环境的私有数据

class Countdown {
    constructor(counter, action) {
        Object.assign(this, {
            dec() {
                if (counter < 1) return;
                counter--;
                if (counter === 0) {
                    action();
                }
            }
        });
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

通过构造函数环境的私有数据 2

class Countdown {
    constructor(counter, action) {
        this.dec = function dec() {
            if (counter < 1) return;
            counter--;
            if (counter === 0) {
                action();
            }
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

通过命名约定的私有数据

class Countdown {
    constructor(counter, action) {
        this._counter = counter;
        this._action = action;
    }
    dec() {
        if (this._counter < 1) return;
        this._counter--;
        if (this._counter === 0) {
            this._action();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

通过 WeakMaps 获取私有数据

const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
    constructor(counter, action) {
        _counter.set(this, counter);
        _action.set(this, action);
    }
    dec() {
        let counter = _counter.get(this);
        if (counter < 1) return;
        counter--;
        _counter.set(this, counter);
        if (counter === 0) {
            _action.get(this)();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

通过符号的私有数据

const _counter = Symbol('counter');
const _action = Symbol('action');

class Countdown {
    constructor(counter, action) {
        this[_counter] = counter;
        this[_action] = action;
    }
    dec() {
        if (this[_counter] < 1) return;
        this[_counter]--;
        if (this[_counter] === 0) {
            this[_action]();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();
于 2018-01-08T23:48:16.550 回答
5

我认为本杰明的答案对于大多数情况来说可能是最好的,直到语言本身支持明确的私有变量。

但是,如果由于某种原因您需要阻止使用 访问Object.getOwnPropertySymbols(),我考虑使用的一种方法是附加一个唯一的、不可配置的、不可枚举的、不可写的属性,该属性可用作构造时每个对象的属性标识符(例如 unique Symbol,如果您还没有其他一些独特的属性,例如 an 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"

如果性能成为问题,这种方法相对于使用 a 的潜在优势WeakMap访问时间更快。

于 2016-08-08T21:06:49.250 回答
5

就我个人而言,我喜欢绑定运算符 的提议,::然后将其与提到的解决方案 @d13 结合起来,但现在坚持使用 @d13 的答案,您可以在export类中使用关键字并将私有函数放入模块中。

还有一个更难的解决方案,这里没有提到,下面是更实用的方法,并允许它在类中拥有所有私有道具/方法。

私有.js

export const get = state => key => state[key];
export const set = state => (key,value) => { state[key] = value; }

测试.js

import { get, set } from './utils/Private'
export default class Test {
  constructor(initialState = {}) {
    const _set = this.set = set(initialState);
    const _get = this.get = get(initialState);

    this.set('privateMethod', () => _get('propValue'));
  }

  showProp() {
    return this.get('privateMethod')();
  }
}

let one = new Test({ propValue: 5});
let two = new Test({ propValue: 8});
two.showProp(); // 8
one.showProp(); // 5

对此发表评论将不胜感激。

于 2017-04-09T18:24:46.157 回答
4

我相信在构造函数中使用闭包可以获得“两全其美”。有两种变体:

所有数据成员都是私有的

function myFunc() {
   console.log('Value of x: ' + this.x);
   this.myPrivateFunc();
}

function myPrivateFunc() {
   console.log('Enhanced value of x: ' + (this.x + 1));
}

class Test {
   constructor() {

      let internal = {
         x : 2,
      };
      
      internal.myPrivateFunc = myPrivateFunc.bind(internal);
      
      this.myFunc = myFunc.bind(internal);
   }
};

有些成员是私人的

注意:这无疑是丑陋的。如果您知道更好的解决方案,请编辑此回复。

function myFunc(priv, pub) {
   pub.y = 3; // The Test object now gets a member 'y' with value 3.
   console.log('Value of x: ' + priv.x);
   this.myPrivateFunc();
}

function myPrivateFunc() {
   pub.z = 5; // The Test object now gets a member 'z' with value 3.
   console.log('Enhanced value of x: ' + (priv.x + 1));
}

class Test {
   constructor() {
      
      let self = this;

      let internal = {
         x : 2,
      };
      
      internal.myPrivateFunc = myPrivateFunc.bind(null, internal, self);
      
      this.myFunc = myFunc.bind(null, internal, self);
   }
};

于 2016-09-20T16:09:24.450 回答
4

事实上,使用符号和代理是可能的。您在类范围内使用符号并在代理中设置两个陷阱:一个用于类原型,以便 Reflect.ownKeys(instance) 或 Object.getOwnPropertySymbols 不会泄露您的符号,另一个用于构造函数本身所以当new ClassName(attrs)被调用时,返回的实例将被拦截并阻止自己的属性符号。这是代码:

const Human = (function() {
  const pet = Symbol();
  const greet = Symbol();

  const Human = privatizeSymbolsInFn(function(name) {
    this.name = name; // public
    this[pet] = 'dog'; // private 
  });

  Human.prototype = privatizeSymbolsInObj({
    [greet]() { // private
      return 'Hi there!';
    },
    revealSecrets() {
      console.log(this[greet]() + ` The pet is a ${this[pet]}`);
    }
  });

  return Human;
})();

const bob = new Human('Bob');

console.assert(bob instanceof Human);
console.assert(Reflect.ownKeys(bob).length === 1) // only ['name']
console.assert(Reflect.ownKeys(Human.prototype).length === 1 ) // only ['revealSecrets']


// Setting up the traps inside proxies:
function privatizeSymbolsInObj(target) { 
  return new Proxy(target, { ownKeys: Object.getOwnPropertyNames });
}

function privatizeSymbolsInFn(Class) {
  function construct(TargetClass, argsList) {
    const instance = new TargetClass(...argsList);
    return privatizeSymbolsInObj(instance);
  }
  return new Proxy(Class, { construct });
}

Reflect.ownKeys()像这样工作:Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj))这就是为什么我们需要为这些对象设置陷阱。

于 2017-02-27T23:12:13.267 回答
4

即使是 Typescript 也做不到。从他们的文档中

当一个成员被标记为私有时,不能从其包含的类之外访问它。例如:

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // Error: 'name' is private;

但在他们的操场上转译,这给出了:

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

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

于 2017-04-18T21:16:07.637 回答
4

参加这个聚会很晚,但是我在搜索中遇到了 OP 问题,所以... 是的,您可以通过将类声明包装在闭包中来拥有私有属性

有一个例子说明我在这个 codepen中如何拥有私有方法。在下面的代码片段中,Subscribable 类有两个“私有”函数processprocessCallbacks. 任何属性都可以以这种方式添加,并通过使用闭包保持私有。如果关注点被很好地分离并且 Javascript 不需要通过添加更多语法而变得臃肿,而闭包巧妙地完成了这项工作,那么 IMO 隐私是一种罕见的需求。

const Subscribable = (function(){

  const process = (self, eventName, args) => {
    self.processing.set(eventName, setTimeout(() => processCallbacks(self, eventName, args)))};

  const processCallbacks = (self, eventName, args) => {
    if (self.callingBack.get(eventName).length > 0){
      const [nextCallback, ...callingBack] = self.callingBack.get(eventName);
      self.callingBack.set(eventName, callingBack);
      process(self, eventName, args);
      nextCallback(...args)}
    else {
      delete self.processing.delete(eventName)}};

  return class {
    constructor(){
      this.callingBack = new Map();
      this.processing = new Map();
      this.toCallbacks = new Map()}

    subscribe(eventName, callback){
      const callbacks = this.unsubscribe(eventName, callback);
      this.toCallbacks.set(eventName,  [...callbacks, callback]);
      return () => this.unsubscribe(eventName, callback)}  // callable to unsubscribe for convenience

    unsubscribe(eventName, callback){
      let callbacks = this.toCallbacks.get(eventName) || [];
      callbacks = callbacks.filter(subscribedCallback => subscribedCallback !== callback);
      if (callbacks.length > 0) {
        this.toCallbacks.set(eventName, callbacks)}
      else {
        this.toCallbacks.delete(eventName)}
      return callbacks}

    emit(eventName, ...args){
      this.callingBack.set(eventName, this.toCallbacks.get(eventName) || []);
      if (!this.processing.has(eventName)){
        process(this, eventName, args)}}}})();

我喜欢这种方法,因为它很好地分离了关注点并保持了真正的私密性。唯一的缺点是需要使用“self”(或类似的东西)来引用私有内容中的“this”。

于 2017-09-07T05:45:21.080 回答
4

是的,完全可以,而且也很容易。这是通过在构造函数中返回原型对象图来公开您的私有变量和函数来完成的。这不是什么新鲜事,但需要一点 js foo 来理解它的优雅。这种方式不使用全局范围或弱映射。它是语言中内置的一种反射形式。取决于你如何利用它;可以强制一个中断调用堆栈的异常,或者将异常作为undefined. 这是在下面演示的,可以在此处阅读有关这些功能的更多信息

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

于 2017-10-12T10:32:24.223 回答
3
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"
于 2016-05-01T22:04:27.810 回答
3

另一种类似于最后两个发布的方式

class Example {
  constructor(foo) {

    // privates
    const self = this;
    this.foo = foo;

    // public interface
    return self.public;
  }

  public = {
    // empty data
    nodata: { data: [] },
    // noop
    noop: () => {},
  }

  // everything else private
  bar = 10
}

const test = new Example('FOO');
console.log(test.foo); // undefined
console.log(test.noop); // { data: [] }
console.log(test.bar); // undefined
于 2017-11-17T16:15:11.920 回答
3

我找到了一个非常简单的解决方案,只需使用Object.freeze(). 当然,问题是您以后不能向对象添加任何内容。

class Cat {
    constructor(name ,age) {
        this.name = name
        this.age = age
        Object.freeze(this)
    }
}

let cat = new Cat('Garfield', 5)
cat.age = 6 // doesn't work, even throws an error in strict mode
于 2018-03-07T07:48:26.017 回答
3

阅读上一个答案我认为这个例子可以总结上述解决方案

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.

于 2019-01-14T08:19:53.760 回答
3

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

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 */

于 2020-04-14T00:23:55.233 回答
2

大多数答案要么说这是不可能的,要么要求您使用 Wea​​kMap 或 Symbol,这些 ES6 功能可能需要 polyfill。然而还有另一种方式!看看这个:

// 1. Create closure
var SomeClass = function() {
  // 2. Create `key` inside a closure
  var key = {};
  // Function to create private storage
  var private = function() {
    var obj = {};
    // return Function to access private storage using `key`
    return function(testkey) {
      if(key === testkey) return obj;
      // If `key` is wrong, then storage cannot be accessed
      console.error('Cannot access private properties');
      return undefined;
    };
  };
  var SomeClass = function() {
    // 3. Create private storage
    this._ = private();
    // 4. Access private storage using the `key`
    this._(key).priv_prop = 200;
  };
  SomeClass.prototype.test = function() {
    console.log(this._(key).priv_prop); // Using property from prototype
  };
  return SomeClass;
}();

// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged

// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged

我将此方法称为访问器模式。基本思想是我们有一个闭包,闭包内有一个,并且我们创建了一个私有对象(在构造函数中),只有在你有的情况下才能访问它。

如果您有兴趣,可以在我的文章中阅读更多相关信息。使用此方法,您可以创建在闭包之外无法访问的每个对象属性。因此,您可以在构造函数或原型中使用它们,但不能在其他任何地方使用它们。我还没有看到这种方法在任何地方使用过,但我认为它真的很强大。

于 2017-04-11T17:26:43.117 回答
2

有关具有私有和公共接口并支持组合的干净简单的“类”解决方案,请参阅此答案

于 2017-09-23T02:09:40.153 回答
2

实际上这可能的。
1.首先,创建类并在构造函数中返回被调用的_public函数。
2. 在被调用的_public函数中传递this引用(以获取对所有私有方法和道具的访问权),以及来自constructor (将被传入的new Names()
的所有参数 3. 在_public函数范围内,还有可以Names访问this(_this的类) 私有Names类的引用

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);
}
于 2017-11-08T15:08:00.340 回答
2

我使用这种模式,它总是对我有用

class Test {
    constructor(data) {
        class Public {
            constructor(prv) {

                // public function (must be in constructor on order to access "prv" variable)
                connectToDb(ip) {
                    prv._db(ip, prv._err);
                } 
            }

            // public function w/o access to "prv" variable
            log() {
                console.log("I'm logging");
            }
        }

        // private variables
        this._data = data;
        this._err = function(ip) {
            console.log("could not connect to "+ip);
        }
    }

    // private function
    _db(ip, err) {
        if(!!ip) {
		    console.log("connected to "+ip+", sending data '"+this.data+"'");
			return true;
		}
        else err(ip);
    }
}



var test = new Test(10),
		ip = "185.167.210.49";
test.connectToDb(ip); // true
test.log(); // I'm logging
test._err(ip); // undefined
test._db(ip, function() { console.log("You have got hacked!"); }); // undefined

于 2018-05-16T13:31:20.007 回答
2

你可以试试这个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;
于 2018-10-11T12:22:24.813 回答
1

在这里,myThing变量是私有的并且是闭包的一部分:

class Person {
  constructor() {

    var myThing = "Hello World";

    return {
      thing: myThing,
      sayThing: this.sayThing
    }
  }

  sayThing() {
    console.log(this.thing);
  }
}

var person = new Person();

console.log(person);
于 2018-02-14T16:05:36.437 回答
1

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

根据 MDN 网络文档

WeakMap 对象是键/值对的集合,其中键只是对象,值可以是任意值。

键中的对象引用被弱保存,这意味着如果不再有对该对象的其他引用,它们就是垃圾收集 (GC) 的目标。

这是使用包含数组的私有成员创建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对象的属性或方法中看到:

在此处输入图像描述

但是,可以使用以下方式访问对象_items 中的私有成员:Queue

const anArray = _items.get(this);
于 2018-12-20T19:34:12.380 回答
1

我意识到这里有几十个答案。我想分享我的解决方案,它可以确保 ES6 类和旧 JS 中的真正私有变量。

var MyClass = (function() {
    var $ = new WeakMap();
    function priv(self) {
       var r = $.get(self);
       if (!r) $.set(self, r={});
       return r;
    }

    return class { /* use priv(this).prop inside your class */ } 
}();

外部世界无法访问 $ 的事实确保了隐私。

当实例消失时,WeakMap 将释放数据。

这绝对适用于纯 Javascript,我相信它们适用于 ES6 类,但我还没有测试 $ 将在成员方法的范围内可用。

于 2021-01-15T17:50:14.313 回答
1

除了给出的答案之外,您还可以通过仅将代理提供给公共代码来使用代理来创建“私有属性”。该实例仅可用于构造函数、绑定方法和代理本身作为receiver.

这比使用 Symbols 和 WeakMaps 有一些优势。

  • 符号是可枚举的,可以使用代理捕获。
  • 当实例代理为 WeakMaps 时,WeakMaps 失败instance !== 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))

这种方法的优点

  • 私有属性访问验证被装饰(可选)到实例上。
  • The private properties are inspectable in the console, debuggers and testing environment and simple properties (no symbols or maps)
  • You control the validation and error handling

The drawbacks

  • The proxy adds overhead and a level of abstraction
  • Debugging will show the Proxy() wrapping the object
  • Methods accessing private props need to be arrow functions
  • You can leak the private props when unintentionally exposing the instance eg. adding a method getSelf = () => this

Notes:

Given the overhead this method could be used in scenarios where property encapsulation and clarity of debugging outweighs the overhead. For example when populating Models from storage. eg. model.setJSON(json) would ensure no private props are mangled.

通过将 WeakMap 与 Proxy 一起使用,可以进一步调整此方法以提供更好的封装,以确保“私有”属性不可见,但允许在每个范围内使用相同的实例访问 WeakMap。然而,这牺牲了可读性和调试。

于 2021-10-10T09:55:38.913 回答
0

正如我们所知,ES6 类不支持私有属性。

以下是我使用的(可能会有所帮助)。基本上我在工厂里封装了一个类。

function Animal(name) {
    const privateData = 'NO experiments on animals have been done!';

    class Animal {
        constructor(_name) {
            this.name = _name;
        }
        getName() {
            return this.name
        }
        getDisclamer() {
            return `${privateData} Including ${this.name}`
        }
    }
    return new Animal(name)
}

我是一个初学者,很高兴听到这是否是一种不好的方法。

于 2018-08-23T09:06:19.200 回答
0

我开发了一个模块,可以帮助您使用名为 Capsulable 的 JavaScript 类中的访问限制。(私人和受保护的静态)

如果您有兴趣,请查看下面我的包裹。 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
于 2018-12-22T02:06:52.690 回答
0

我有一个可行的解决方法,它非常简单......虽然性能可能不是害虫......但它有效并且运作良好。

诀窍是,在建立私有属性和功能并且需要标准化/采用的变通方法之前,这是另一种变通方法......

class ClassPrivateProperties {
    constructor(instance) {
        const $this = instance;
        let properties = {};
        this.prop = (key, value = undefined) => {
            if (!value) {
                return properties[key];
            } else {
                properties[key] = value;
            }
        };
        this.clear = instance => {
            if ($this === instance) {
                properties = {};
                return true;
            } else {
                return false;
            }
        }
    }
}

这是一个示例用法,可以是任何情况(如果您使用上述内容,请随意使其更好)

class Test {
    constructor() {
        this._privateProps = new ClassPrivateProperties(this);
    }
    property(key, value = undefined) {
        if (!value) {
            return this._privateProps.prop(key);
        } else {
            this._privateProps.prop(key, value);
        }
    }
    clear() { return this._privateProps.clear(this); }
}
const test = new test;
test.property('myKey','some value here');
console.log(test.property('myKey'));

就像我提到的那样,这可能不是最好的,但它可以工作并使属性真正私有。

于 2020-03-12T17:51:47.543 回答
0

我们可以使用 getter 和 setter 模拟类的私有属性。

例如 1

class FootballClub {
    constructor (cname, cstadium, ccurrentmanager) {
        this.name = cname;
        this._stadium  = cstadium;  //  we will treat this prop as private and give getter and setter for this.
        this.currmanager = ccurrentmanager;
    }

    get stadium( ) {
        return this._stadium.toUpperCase();
    }

}

let club = new FootballClub("Arsenal", "Emirates" , "Arteta")
console.log(club);
//FootballClub {
//    name: 'Arsenal',
//    _stadium: 'Emirates',
//    currmanager: 'Arteta'
//  }
console.log( club.stadium ); // EMIRATES
club.stadium = "Highbury"; // TypeError: Cannot set property stadium of #<FootballClub> which has only a getter

在上面的示例中,我们没有为 Stadium 提供 setter 方法因此我们无法为此设置新值。在接下来的例子中,为体育场添加了一个二传手

例如 2

class FootballClub {
    constructor (cname, cstadium, ccurrentmanager) {
        this.name = cname;
        this._stadium  = cstadium;  //  we will treat this prop as private and give getter and setter for this.
        this.currmanager = ccurrentmanager;
    }

    get stadium( ) {
        return this._stadium.toUpperCase();
    }

    set stadium(val) {
       this._stadium = val;
    }
}

let club = new FootballClub("Arsenal", "Emirates" , "Arteta")
console.log(club.stadium); // EMIRATES
club.stadium = "Emirates Stadium";
console.log(club.stadium); // EMIRATES STADIUM
于 2020-06-08T07:21:48.397 回答
0

根据 ES2022,我们可以在 JavaScript 类中添加私有属性和方法。

我们可以通过预先定义私有字段的#名称来定义它们。

演示:

class Something {
  #property = "test"; // By adding # followed with the property name we are making it private.
}

var instance = new Something();

console.log(instance.#property); // Uncaught SyntaxError: Private field '#property' must be declared in an enclosing class

于 2022-02-23T18:21:07.163 回答