JavaScript ES6类中的私有属性

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

这里有一个例子。 如何防止访问instance.property?< / p >
class Something {
constructor(){
this.property = "test";
}
}


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

取决于你问谁:-)

没有private属性修饰符包含在最大最小类提案中,它似乎已经进入了目前的草案

但是,可能存在支持 < < em >私人名称/ em >,它允许私有属性—它们也可能用于类定义中。

更新:具有更好语法的提案正在路上。欢迎投稿。


是的,在对象中有-用于范围访问- 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

答案是“不”。但是你可以像这样创建对属性的私有访问:

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

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

下面是一个更清晰、有效的例子:

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

更新:看别人的回答,这个已经过时了。

简单的回答,不,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; }
}

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

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

是的,用#作为名称的前缀,并将其包含在类定义中,而不仅仅是构造函数中。

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

例子:

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

WeakMap可以用来提高上述方法的性能,但代价是更加混乱。WeakMap将数据与对象(这里是类实例)相关联,只能使用该WeakMap访问数据。因此,我们使用作用域变量方法创建一个私有弱映射,然后使用该弱映射检索与this相关的私有数据。这比作用域变量方法更快,因为所有实例都可以共享一个WeakMap,所以不需要重新创建方法来访问它们自己的WeakMap。

例子:

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

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

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

使用 word创建类

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

为了允许对这些变量进行一些访问控制(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

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

something.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在评论中概述的那样,这些私有属性是静态的,因此在全球范围内。它们在处理单例对象时工作得很好,但在处理瞬态对象时必须小心。扩展上面的例子:

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

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

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

something.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

# EYZ0

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

# EYZ0

一个运行的代码片段可以在这里找到:http://www.webpackbin.com/NJgI5J8lZ

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"

我认为本杰明的回答可能是大多数情况下最好的,直到语言本身支持显式私有变量。

然而,如果出于某种原因,您需要阻止对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的潜在优势是更快的访问时间

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

所有数据成员都是私有的

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

事实上,使用符号和代理是可能的。您可以使用类作用域中的符号,并在代理中设置两个陷阱:一个用于类原型,以便Reflect.ownKeys(实例)或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)),这就是为什么我们需要一个陷阱这些对象。

就我个人而言,我喜欢绑定运营商 ::的建议,然后将它与@d13提到的解决方案结合起来,但现在坚持@d13的答案,在那里你使用export关键字为你的类,并将私有函数放在模块中。

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

Private.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

请对此提出意见。

大多数答案要么说不可能,要么要求你使用WeakMap或Symbol,这是ES6的特性,可能需要腻子。然而,还有另一种方法!看看这个:

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

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

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

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

当成员被标记为private时,就不能从其包含类的外部访问它。例如:

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

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

WeakMap

  • IE11支持(符号不支持)
  • hard-private(由于Object.getOwnPropertySymbols,使用符号的道具是soft-private)
  • 看起来非常干净(不像闭包需要构造函数中的所有道具和方法)

首先,定义一个函数来包装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不支持,但在示例中它看起来更简洁。

来这个派对很晚,但我在搜索中碰到了OP问题,所以… 是的,您可以通过将类声明包装在闭包中来拥有私有属性

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

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'。

这个答案,一个干净的&简单的“类”解决方案,具有私有和公共接口,并支持组合

不同的“私人”方式

我决定采取一种更实用的方法,如果你的IDE支持JSDoc(例如,Webstorm),而不是与ES6中私有可见性目前不可用的事实作斗争。这个想法是使用# EYZ0标签。在开发过程中,IDE将阻止您从类外部访问任何私有成员。对我来说工作得很好,它对于隐藏内部方法非常有用,因此自动完成功能向我展示了该类真正想要公开的内容。这里有一个例子:

auto-complete显示just public stuff

是的,完全可以,而且很容易。这是通过在构造函数中返回原型对象图来公开私有变量和函数来实现的。这并不是什么新鲜事,但是稍微了解一下js foo就能理解它的优雅之处。这种方法不使用全局作用域或弱映射。这是一种内置在语言中的反映形式。这取决于你如何利用它;可以强制一个中断调用堆栈的异常,也可以将异常隐藏为undefined。这是demonstarted下面,可以阅读更多关于这些功能在这里

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

实际上它可能 1. 首先,创建类,并在构造函数中返回被调用的_public函数 2. 在被调用的_public函数中,传递this引用(获取所有私有方法和道具的访问权限),以及constructor (将在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);
}

另一种方式类似于上两个帖子

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

在寻找“类的私有数据”的最佳实践时,我偶然看到了这篇文章。上面提到了一些模式会有性能问题。

我根据在线书籍“探索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();

这里,神话变量是私有的,是闭包的一部分:

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

我找到了一个非常简单的解决方案,只需使用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

我用这个模式,它总是对我有效

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

正如我们所知,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)
}

我是一个初学者,所以很高兴听到这是一个坏的方法。

私有类特性第三阶段提案中。它的大部分特性都是由所有主流浏览器使用的支持

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

你可以试试这个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 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)
}

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

# EYZ0:

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

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

这是一个创建带有私有成员_itemsQueue数据结构的例子,该成员包含一个数组。

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对象的属性或方法中看到:

enter image description here

然而,在Queue对象中的私有成员_items 可以通过以下方式访问:

const anArray = _items.get(this);
我已经开发了一个模块,帮助您使用访问限制在 JavaScript类,叫做Capsulable。(私人,保护静态)< / p >

如果你感兴趣,看看我下面的包。 # EYZ0 < / p >

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

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

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

我有一个解决办法,它很简单…虽然性能不是问题…但它确实有效,而且效果很好。

诀窍在于,在私有属性和函数被建立和标准化/采用之前,需要另一种解决方案,这是另一种解决方案…

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'));

就像我提到的,这个问题不是最好的,但它工作,使属性真正的私有。

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

< a href = " https://codesandbox。Io /s/class-demo-837bj" rel="nofollow noreferrer">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 */

我们可以使用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

在上面的例子中,我们没有给出体育场的setter方法,因此我们不能为它设置一个新值。在下一个eg中,为体育场添加了一个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

我意识到这里有很多答案。我想分享我的解决方案,它确保了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类中工作,但我还没有测试$将在成员方法的范围内可用。

除了给出的答案,您还可以使用代理来创建“私有财产”。通过使代理只对公共代码可用。该实例仅对构造函数、绑定方法和代理本身可用,如receiver

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

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

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

注:

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

这种方法可以通过使用WeakMap和代理来提供更好的封装,以确保“私有”。属性是不可见的,但允许在每个作用域上使用相同的实例访问WeakMap。然而,这牺牲了可读性和调试。

根据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