是否可以在 ES6 类中创建私有属性?
这是一个例子。如何阻止对 instance.property
的访问?
class Something {
constructor(){
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); //=> "test"
[Scopes]
对象中显示整个执行上下文。有些事情只需要在浏览器范围之外进行编码。在我的测试中,没有任何方法可以隐藏 Chrome 中的任何内容。
简短的回答,不,没有对 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; }
}
Private class features 在 Stage 3 proposal 中。它的大部分功能supported被所有主要浏览器所采用。
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
#beep() {}
;这:async #bzzzt() {}
?
_
将是一个重大更改,除非您的意思是 JS 根本不需要 private 私有属性
是的,在名称前加上 #
并将其包含在类定义中,而不仅仅是构造函数。
真正的私有属性终于在 ES2022 中添加了。截至 2022 年 3 月 22 日,所有主流浏览器都支持私有属性(字段和方法)至少六个月,但仍有 10-20% 的用户使用旧版浏览器 [Can I Use]。
例子:
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 可用于提高上述方法的性能,以换取更多的混乱。 WeakMaps 将数据与对象(此处为类实例)相关联,使得只能使用该 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
此示例使用带有 Object 键的 WeakMap 来将一个 WeakMap 用于多个私有属性;您还可以使用多个 WeakMap 并像 privateAge.set(this, 20)
一样使用它们,或者编写一个小包装器并以另一种方式使用它,例如 privateProps.set(this, 'age', 0)
。
这种方法的隐私理论上可以通过篡改全局 WeakMap
对象来破坏。也就是说,所有 JavaScript 都可以被损坏的全局变量破坏。
(这个方法也可以用 Map
完成,但 WeakMap
更好,因为 Map
会造成内存泄漏,除非你非常小心,为此,两者并没有什么不同。)
半答案:作用域符号
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
中。
半答案:下划线
旧的约定是只使用带有下划线前缀的公共属性。这并不能保持隐私,但它确实很好地与读者沟通,他们应该将其视为隐私,这通常可以完成工作。作为交换,我们得到了一种比其他解决方法更易于阅读、更易于键入且速度更快的方法。
例子:
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:很棒,但尚未得到所有访问者的支持
作用域变量:私有的、较慢的、笨拙的
Scoped WeakMaps:可破解,笨拙
Scoped Symbols:可枚举和可破解,有点尴尬
下划线:只是对隐私的要求,没有其他缺点
instanceof
。我承认我认为这种方法只是为了完整性,应该更多地考虑它的实际能力。
更新:proposal with nicer syntax 正在路上。欢迎投稿。
是的,有 - 用于对象中的范围访问 - ES6 introduces Symbol
s。
符号是唯一的,除了反射(如 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
Object.getOwnPropertySymbols
吗? ;)
const myPrivateMethod = Math.random(); Something.prototype[''+myPrivateMethod] = function () { ... } new Something()[''+myPrivateMethod]();
。这不是真正的隐私,而是传统 JavaScript 意义上的晦涩难懂。我认为“私有”JavaScript 意味着使用闭包来封装变量。因此,这些变量无法通过反射访问。
private
和 protected
关键字会比 Symbol
或 Name
干净得多。我更喜欢点符号而不是括号符号。我想继续使用点来处理私人事务。 this.privateVar
答案是不”。但是您可以创建对属性的私有访问,如下所示:
使用模块。模块中的所有内容都是私有的,除非使用 export 关键字将其公开。
在模块内部,使用函数闭包:http://www.kirupa.com/html5/closures_in_javascript.htm
(在早期版本的 ES6 规范中,Symbol 可用于确保隐私的建议是正确的,但不再是这种情况:https://mail.mozilla.org/pipermail/es-discuss/2014-January/035604.html 和 https://stackoverflow.com/a/22280202/1282216。有关 Symbol 和隐私的详细讨论,请参阅:https://curiosity-driven.org/private-properties-in-javascript)
在 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));
}
}
get
数量减少到每个方法一个(例如 const _ = privates.get(this); console.log(_.privateProp1);
)。
const myObj = new SomeClass(); console.log(privateProp1.get(myObj)) // "I am Private1"
这意味着您的财产是否是私有的?
为了将来其他旁观者的参考,我现在听说建议是使用 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}`)
}
}
}
}
取决于 whom you ask :-)
Maximally minimal classes proposal 中没有包含 private
属性修饰符,似乎已将其纳入 current draft。
但是,可能有 support for private names,它确实允许私有属性 - 它们也可能在类定义中使用。
使用 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
private static
。
a.say(); // a
应该是 b.say(); // b
let _message = null
的方式,不是很酷,当多次调用构造函数时,它搞砸了。
是的 - 您可以创建封装的属性,但至少没有使用 ES6 访问修饰符(公共|私有)来完成。
这是一个简单的例子,它是如何用 ES6 完成的:
1 使用 class 词创建类
在它的构造函数中使用 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
new Something();
时都会创建每个实例方法的副本,因为你的方法在构造函数中声明为访问这些私有变量。如果您创建大量类的实例,这可能会导致大量内存消耗,从而导致性能问题。方法应该在构造函数范围之外声明。我的评论更多的是对您的解决方案缺陷的解释,而不是批评。
完成@d13 以及@johnny-oshika 和@DanyalAytekin 的评论:
我想在@johnny-oshika 提供的示例中,我们可以使用普通函数而不是箭头函数,然后将它们与当前对象加上 _privates
对象作为咖喱参数 .bind
:
东西.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 和 _updateMessage 就像私有方法一样,只要我们不导出引用)
尽管它们不在原型上,但上述方法将节省内存,因为实例在类外部创建一次(而不是在构造函数中定义它们)
我们不会泄漏任何全局变量,因为我们在模块中
我们还可以使用绑定的 _privates 对象来拥有私有属性
我能想到的一些缺点:
不太直观
混合使用类语法和老派模式(对象绑定、模块/函数范围的变量)
硬绑定 - 我们无法重新绑定公共方法(尽管我们可以通过使用软绑定来改进这一点(https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26% 20object%20prototypes/ch2.md#softening-binding))
可在此处找到运行代码段:http://www.webpackbin.com/NJgI5J8lZ
“私人”的不同方法
我决定采用一种更实用的方法,如果你的 IDE 支持 JSDoc(例如,Webstorm),它就可以很好地解决这个问题,而不是反对 ES6 中当前不可用的私有可见性这一事实。我们的想法是使用 @private
tag。就开发而言,IDE 将阻止您从其类之外访问任何私有成员。对我来说效果很好,它对于隐藏内部方法非常有用,因此自动完成功能向我展示了该类真正想要公开的内容。这是一个例子:
https://i.stack.imgur.com/F6jXB.png
@private
评论不能阻止这些,它只是文档生成的Feature,而您是 IDE。
哦,这么多奇特的解决方案!我通常不关心隐私,所以我使用 “伪隐私”,因为它是 said here。但是如果确实关心(如果有一些特殊要求),我会在这个例子中使用类似的东西:
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)
}
弱地图
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,但在示例中看起来更简洁。
我在寻找“课程私有数据”的最佳实践时遇到了这篇文章。有人提到,一些模式会出现性能问题。
我根据在线书籍“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();
我个人喜欢 bind operator ::
的提议,然后将其与提到的解决方案 @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
对此发表评论将不胜感激。
我认为 Benjamin's answer 在大多数情况下可能是最好的,直到语言本身支持明确的私有变量。
但是,如果由于某种原因您需要阻止使用 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
的潜在优势是 faster access time。
destroy()
方法,只要需要删除对象,就应该使用代码调用该方法。
我相信在构造函数中使用闭包可以获得“两全其美”。有两种变体:
所有数据成员都是私有的
function myFunc() { console.log('x 的值:' + this.x); this.myPrivateFunc(); } function myPrivateFunc() { console.log('x 的增强值:' + (this.x + 1)); } class Test { constructor() { let internal = { x : 2, }; internal.myPrivateFunc = myPrivateFunc.bind(internal); this.myFunc = myFunc.bind(内部); } };
有些成员是私人的
注意:这无疑是丑陋的。如果您知道更好的解决方案,请编辑此回复。
函数 myFunc(priv, pub) { pub.y = 3; // Test 对象现在获得一个值为 3 的成员 'y'。 console.log('Value of x: ' + priv.x); this.myPrivateFunc(); } 函数 myPrivateFunc() { pub.z = 5; // Test 对象现在获得一个值为 3 的成员 'z'。 console.log('Enhanced value of x: ' + (priv.x + 1)); } 类测试 { 构造函数() { 让自我 = 这个;让内部 = { x : 2, }; internal.myPrivateFunc = myPrivateFunc.bind(null, internal, self); this.myFunc = myFunc.bind(null, internal, self); } };
事实上,使用符号和代理是可能的。您在类范围内使用符号并在代理中设置两个陷阱:一个用于类原型,以便 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]() + ` 宠物是a ${this[pet]}`); } }); return Human; })(); const bob = new Human('Bob'); console.assert(bob instanceof Human); console.assert(Reflect.ownKeys(bob).length === 1) // 仅 ['name'] console.assert(Reflect.ownKeys(Human.prototype).length === 1 ) // 仅 ['revealSecrets '] // 设置代理内部的陷阱: function privatizeSymbolsInObj(target) { return new Proxy(target, { ownKeys: Object.getOwnPropertyNames }); } 函数 privatizeSymbolsInFn(Class) { 函数构造(TargetClass, argsList) { const instance = new TargetClass(...argsList);返回privatizeSymbolsInObj(实例); } 返回新的代理(类,{构造}); }
Reflect.ownKeys()
的工作方式如下:Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj))
这就是我们需要为这些对象设置陷阱的原因。
即使是 Typescript 也做不到。从他们的 documentation:
当一个成员被标记为私有时,不能从其包含的类之外访问它。例如:类动物{私人名称:字符串;构造函数(名称:字符串){ this.name = theName; } } new Animal("Cat").name; // 错误:'name' 是私有的;
但是在他们的 playground 上进行了编译,这给出了:
var Animal = (function () {
function Animal(theName) {
this.name = theName;
}
return Animal;
}());
console.log(new Animal("Cat").name);
所以他们的“私人”关键字是无效的。
参加这个聚会很晚,但是我在搜索中遇到了 OP 问题,所以...是的,您可以通过将类声明包装在闭包中来拥有私有属性
this codepen 中有一个关于我如何使用私有方法的示例。在下面的代码片段中,Subscribable 类有两个“私有”函数 process
和 processCallbacks
。任何属性都可以以这种方式添加,并通过使用闭包保持私有。如果关注点被很好地分离并且 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”。
是的,完全可以,而且也很容易。这是通过在构造函数中返回原型对象图来公开您的私有变量和函数来完成的。这不是什么新鲜事,但需要一点 js foo 来理解它的优雅。这种方式不使用全局范围或弱映射。它是语言中内置的一种反射形式。取决于你如何利用它;可以强制一个中断调用堆栈的异常,或者将异常作为 undefined
掩埋。这在下面进行了演示,可以阅读有关这些功能的更多信息here
类 Clazz { 构造函数() { var _level = 1 函数 _private(x) { return _level * x; } return { 级别:_level,public:this.private,public2:function(x) { return _private(x); }, public3: function(x) { return _private(x) * this.public(x); }, }; } 私有(x){ 返回 x * x; } } var clazz = 新的 Clazz(); console.log(clazz._level); //未定义的console.log(clazz._private); // 未定义的 console.log(clazz.level); // 1 console.log(clazz.public(1)); //1 控制台.log(clazz.public2(2)); //2 控制台.log(clazz.public3(3)); //27 console.log(clazz.private(0)); //错误
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"
console.log(instance.property)
应该抛出或给你未定义,而不是给你回“测试”。
另一种类似于最后两个发布的方式
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
我找到了一个非常简单的解决方案,只需使用 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
setName(name) { this.name = name; }
这样的 setter 方法
此代码演示私有和公共、静态和非静态、实例和类级别、变量、方法和属性。
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 // 实例公共属性 Animal.count++ let InstancePriVar = 'InstancePriVar: ' + kind / / 实例私有构造函数-var log(InstancePriVar) Animal.#ClassPriVar += 3 this.adhoc = 'adhoc' // 实例公共属性没有构造函数-参数 } #PawCount = 4 // 实例私有变量集 Paws(newPawCount) { // 实例 public prop this.#PawCount = newPawCount } get Paws() { // 实例 public prop return this.#PawCount } get GetPriVar() { // 实例 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不支持 get #PrivMeth(){ // instance private prop return Animal.#ClassPriVar + 'Private Method' } static get #PrivMeth(){ // class priva te prop return Animal.#ClassPriVar + ' Private Method' } */ } function log(str) { console.log(str) } // TESTING log(Animal.count) // 静态,无实例可用 log(Animal .GetPriVarStat) // 静态,无实例可用 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) 让 B = new Animal('Dog') log(Animal.count + ': ' + B.kind) log(B.GetPriVar) log(A.GetPriVar) // 返回与 B.GetPriVar 相同的结果。行为类似于类级属性,但调用类似于实例级属性。这是因为 non-stat fx 需要实例。 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) // 私有实例 var log('PawCount: ' + A.PawCount) // private.使用 getter /* log('PawCount: ' + A.#PawCount) // 私有的。使用 getter log('PawCount: ' + Animal.#PawCount) // 实例和私有。使用吸气剂 */
阅读上一个答案我认为这个例子可以总结上述解决方案
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
。
大多数答案要么说这是不可能的,要么要求您使用 WeakMap 或 Symbol,这些 ES6 功能可能需要 polyfill。然而还有另一种方式!看看这个:
// 1. 创建闭包 var SomeClass = function() { // 2. 在闭包中创建 `key` var key = {}; // 创建私有存储的函数 var private = function() { var obj = {}; // return 使用 `key` 访问私有存储的函数 return function(testkey) { if(key === testkey) return obj; // 如果 `key` 错误,则无法访问存储 console.error('Cannot access private properties');返回未定义; }; }; var SomeClass = function() { // 3. 创建私有存储 this._ = private(); // 4. 使用 `key` 访问私有存储 this._(key).priv_prop = 200; }; SomeClass.prototype.test = function() { console.log(this._(key).priv_prop); // 使用原型的属性 };返回某类; }(); // 可以从原型中访问私有属性 var instance = new SomeClass();实例.test(); // `200` 记录 // 不能从闭包外部访问私有属性 var wrong_key = {}; instance._(wrong_key); // 不明确的;记录错误
我称这种方法访问器模式。基本思想是我们有一个闭包,闭包内有一个键,并且我们创建了一个私有对象(在构造函数中),只有在你有键的情况下才能访问它。
如果您有兴趣,可以在 my article 中阅读更多相关信息。使用此方法,您可以创建在闭包之外无法访问的每个对象属性。因此,您可以在构造函数或原型中使用它们,但不能在其他任何地方使用它们。我还没有看到这种方法在任何地方使用过,但我认为它真的很强大。
我使用这种模式,它总是对我有用
class Test { constructor(data) { class Public { constructor(prv) { // 公共函数(必须在构造函数中才能访问“prv”变量) connectToDb(ip) { prv._db(ip, prv._err); } } // 没有访问“prv”变量的公共函数 log() { console.log("I'm logging"); } } // 私有变量 this._data = data; this._err = function(ip) { console.log("无法连接到 "+ip); } } // 私有函数 _db(ip, err) { if(!!ip) { console.log("连接到 "+ip+", 发送数据 '"+this.data+"'");返回真; } 其他错误(IP); } } var test = new Test(10), ip = "185.167.210.49"; test.connectToDb(ip); // true test.log(); // 我正在记录 test._err(ip); // undefined test._db(ip, function() { console.log("你被黑了!"); }); // 不明确的
getName
和setName
属性设为私有?_name
是真正私有的。