关于类:JavaScript ES6类中的私有属性

Private properties in JavaScript ES6 classes


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


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

1
2
3
4
5
6
7
8
class Something {
  constructor(){
    this.property ="test";
  }
}

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



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


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


ES6

1
2
3
4
5
6
7
class Person {
    constructor(name) {
        var _name = name
        this.setName = function(name) { _name = name; }
        this.getName = function() { return _name; }
    }
}


ES5

1
2
3
4
5
function Person(name) {
    var _name = name
    this.setName = function(name) { _name = name; }
    this.getName = function() { return _name; }
}



要扩展@ loganfsmyth的答案:


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

范围变量


这里的方法是使用私有的构造函数的范围来存储私有数据。对于可以访问此私有数据的方法,它们也必须在构造函数中创建,这意味着您将使用每个实例重新创建它们。这是一种表现和记忆惩罚,但有些人认为惩罚是可以接受的。对于不需要访问私有数据的方法,可以通过像往常一样将它们添加到原型来避免惩罚。


例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

Scoped WeakMap


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


例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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用于多个私有属性;你也可以使用多个WeakMaps并像age.set(this, 20)一样使用它们,或者写一个小包装器并以另一种方式使用它,比如privateProps.set(this, 'age', 0)


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


(这种方法也可以用Map完成,但WeakMap更好,因为Map会造成内存泄漏,除非你非常小心,为此目的,两者并没有其他不同。)

半答案:Scoped符号


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


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


例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)`.

半答案:下划线


旧的默认值,只需使用带下划线前缀的公共属性。虽然这不是一个私人财产,但这种惯例非常普遍,以至于它能够很好地传达读者应该将财产视为私有财产,这通常可以完成工作。为了换取这种失误,我们得到了一种更容易阅读,更容易打字和更快速的方法。


例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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开始,仍然没有完美的私有财产方式。各种方法都有利有弊。范围变量是真正的私有;作用域WeakMaps非常私密,比范围变量更实用;范围符号是相当私密和合理实用的;下划线通常足够私密且非常实用。



更新:具有更好语法的提案即将推出。欢迎捐款。


是的,对于对象中的作用域访问 - ES6引入了Symbol


符号是唯一的,你不能从外部访问一个除了反射(如Java / C#中的私有),但任何有权访问内部符号的人都可以使用它来进行密钥访问:

1
2
3
4
5
6
7
8
9
10
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



私有领域正在ECMA标准中实施。您现在可以使用babel 7和第3阶段预设开始使用它们。请参阅babel REPL示例。

1
2
3
4
5
6
7
8
9
10
class Something {
  #property;

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

const instance = new Something();
console.log(instance.property); //=> undefined



答案是不"。但您可以创建对此属性的私有访问权限:

  • 使用模块。除非使用export关键字公开,否则模块中的所有内容都是私有的。
  • 在模块内部,使用函数闭包:http://www.kirupa.com/html5/closures_in_javascript.htm


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



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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是如此动态。有人还可以

1
2
3
4
5
var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
    // Store 'this', 'key', and 'value'
    return oldSet.call(this, key, value);
};


在存储时捕获值,因此如果您想要格外小心,则需要捕获对.set.get的本地引用以明确使用而不是依赖于可覆盖的原型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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));
  }
}



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


这是一个更清晰,更有效的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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}`)
      }
    }
  }
}



取决于你问谁:-)


没有private属性修饰符包含在最小极限类提议中,它似乎已成为当前草稿。


但是,可能会支持私有名称,它允许私有属性 - 它们也可能在类定义中使用。



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

something.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let _message = null;
const _greet = name => {
  console.log('Hello ' + name);
};

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

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


然后消费代码可能如下所示:

1
2
3
4
5
6
import Something from './something.js';

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

更新(重要):


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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对象作为curried参数:


something.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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就像私有方法一样,只要我们不export引用)
  • 虽然它们不在原型上,但上面提到的方法将节省内存,因为实例在类之外创建一次(而不是在构造函数中定义它们)
  • 因为我们在模块中,所以我们不会泄漏任何全局变量
  • 我们也可以使用绑定的_privates对象拥有私有属性


我能想到的一些缺点:

  • 不太直观
  • 类语法和旧学校模式的混合使用(对象绑定,模块/函数范围变量)
  • 硬绑定 - 我们无法重新绑定公共方法(虽然我们可以通过使用软绑定来改进它(https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26% 20object%20prototypes / ch2.md#软化结合))


可以在此处找到正在运行的代码段:http://www.webpackbin.com/NJgI5J8lZ



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


这是一个如何使用ES6完成的简单示例:


1使用类词创建类


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


3要允许对这些变量进行某些访问控制(setter | getters),您可以使用以下命令在其构造函数中声明实例方法:this.methodName=function(){}语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"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;
            }
        }
    }


现在让我们检查一下:

1
2
3
4
5
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


一种不同的"私人"方法


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


auto-complete showing just public stuff



WeakMap

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


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

1
2
3
4
5
6
7
8
9
10
11
function Private() {
  const map = new WeakMap();
  return obj => {
    let props = map.get(obj);
    if (!props) {
      props = {};
      map.set(obj, props);
    }
    return props;
  };
}


然后,在你的课外构建一个引用:

1
2
3
4
5
6
7
8
9
10
11
12
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不支持类,但在示例中看起来更干净。



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


但是,如果由于某种原因你需要阻止使用Object.getOwnPropertySymbols()进行访问,我考虑使用的方法是附加一个唯一的,不可配置的,不可枚举的,不可写的属性,可以用作每个属性的属性标识符。构造上的对象(例如唯一的Symbol,如果你还没有像id这样的其他独特属性)。然后使用该标识符保留每个对象的"私有"变量的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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的潜在优势是更快的访问时间。



我相信使用构造函数内部的闭包可以获得"两个世界中最好的"。有两种变化:


所有数据成员都是私有的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
   }
};


有些成员是私人的


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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)时,将拦截返回的实例并阻止自己的属性符号。
这是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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

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


Test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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


对它的评论将不胜感激。



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


我根据在线书"探索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种基本模式是:


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();


私有数据通过命名约定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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();


私人数据通过符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();

1
2
3
4
5
6
7
8
9
10
11
12
13
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"



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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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)}}}})();


我喜欢这种方法,因为它可以很好地区分问题并保持真正的私密性。唯一的缺点是需要使用"自我"(或类似的东西)在私人内容中引用"此"。



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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的另一种可能的实现:

1
2
3
4
5
function Job(name){
  var impl = new jobImpl(name);
  this.do = time => impl.do(time),
  this.stop = time => impl.stop(time)
}


即使是打字稿也无法做到。从他们的文件:

When a member is marked private, it cannot be accessed from outside of its containing class. For example:

1
2
3
4
5
6
class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

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


但在他们的操场上发现,这给出了:

1
2
3
4
5
6
7
var Animal = (function () {
    function Animal(theName) {
        this.name = theName;
    }
    return Animal;
}());
console.log(new Animal("Cat").name);


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



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

1
2
3
4
5
6
7
8
9
10
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



您可以试试这个https://www.npmjs.com/package/private-members


该包将按实例保存成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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);
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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)); //2
console.log(clazz.public2(2)); //2
console.log(clazz.public3(3)); //27
console.log(clazz.private(0)); //error



看到这个答案,一个干净的&amp;简单的"类"解决方案,具有私有和公共界面,并支持合成



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 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


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


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



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


根据MDN网站文档:

The WeakMap object is a collection of key/value pairs in which the keys are objects only and the values can be arbitrary values.

The object references in the keys are held weakly, meaning that they are a target of garbage collection (GC) if there is no other reference to the object anymore.


这是使用包含数组的私有成员_items创建Queue数据结构的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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];
    }    
}


使用示例:

1
2
3
4
5
6
7
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

1
const anArray = _items.get(this);


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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;
    }
})();


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


如果您有兴趣,请查看我的包裹。
https://github.com/hmmhmmhm/capsulable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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


我们知道ES6类的私有属性没有原生支持。


以下是我使用的(可能会有帮助)。基本上我是在工厂内包装一个班级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
}


我是一个初学者,很高兴听到这是一个糟糕的方法。