JavaScript ES6类中的私有属性


444

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

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

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

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

5
对于此功能,实际上有第3阶段的提案-tc39.github.io/proposal-class-fields github.com/tc39/proposal-class-fields
arty

@arty我用示例提供了答案: stackoverflow.com/a/52237988/1432509
Alister

Answers:


165

专用字段(和方法)正在ECMA标准中实现。您可以从babel 7和stage 3预设开始使用它们。

class Something {
  #property;

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

  #privateMethod() {
    return 'hello world';
  }

  getPrivateMessage() {
      return this.#privateMethod();
  }
}

const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> hello world

我想知道这些类字段如何工作。您目前无法this在构造函数中使用,然后再调用super()。但是通天塔把它们放在超级之前。
seeker_of_bacon

如何配置ESLint以允许#privateCrap语法?
Marecky

6
那埃斯林特呢?等号出现解析器错误。Babel正在工作,只是eslint无法解析这种新的js语法。
martonx

6
哇,这真丑。标签是有效字符。该财产不是真的私有,还是?..我在TypeScript中检查了它。私有成员不是以私有或只读方式(从外部)编译的。就像另一个(公共)属性一样声明。(ES5)。
多米尼克

2
您如何用此编写私有方法?我可以这样做:#beep() {}; 这:async #bzzzt() {}
КонстантинВан

277

简短的答案,不,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; }
}

1
我最喜欢这种解决方案。我同意不应将其用于缩放,但它对于通常每个包含仅实例化一次的类非常理想。
布莱克·雷贾利亚

2
另外,每次创建新类时,您都将重新定义此类的每个组件。
昆汀·罗伊

10
太奇怪了!在ES6中,您将创建比ES6之前更多的“封闭金字塔”!在构造函数中定义函数看起来比上面的ES5示例丑陋。
Kokodoko

1
由于OP专门询问ES6类,因此我个人认为这是一个较差的解决方案,即使在技术上可行。主要的局限性在于,现在必须在构造函数内部声明所有使用私有变量的类方法,从而严重损害class了首先使用语法的优势。
NanoWizard

10
这一切都是引入间接的。现在如何将getNamesetName属性设为私有?
aij

195

扩展@loganfsmyth的答案:

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

范围变量

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

例:

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

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

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

// here we can access name but not age

作用域WeakMap

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

例:

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

  class Person {
    constructor(name) {
      this.name = name; // this is public
      privateProps.set(this, {age: 20}); // this is private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${privateProps.get(this).age}`);
    }
  }

  return Person;
})();

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

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

本示例使用一个对象将一个WeakMap用于多个私有属性。您还可以使用多个WeakMaps并像一样使用它们age.set(this, 20),或者编写一个小的包装器并以其他方式使用它,例如privateProps.set(this, 'age', 0)

从理论上讲,此方法的私密性可以通过篡改全局WeakMap对象来破坏。就是说,所有JavaScript都可能被混乱的全局变量破坏。我们的代码已经基于没有发生这种情况的假设而构建。

(此方法也可以用来完成Map,但WeakMap更好,因为Map除非您非常小心,否则会造成内存泄漏,因此,两者没有其他区别。)

半答案:范围符号

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

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

例:

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

  class Person {
    constructor(name) {
      this.name = name; // this is public
      this[ageKey] = 20; // this is intended to be private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${this[ageKey]}`);
    }
  }

  return Person;
})();

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

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

半答案:下划线

旧的默认值,仅使用带有下划线前缀的公共属性。尽管不是任何形式的私有财产,但这种约定足够普遍,以至于它很好地传达了读者应该将财产视为私有的知识,这通常可以完成工作。作为对这一失误的交换,我们得到了一种更易于阅读,更易于键入和更快的方法。

例:

class Person {
  constructor(name) {
    this.name = name; // this is public
    this._age = 20; // this is intended to be private
  }

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

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

// Here we can access both joe's name and age. But we know we aren't
// supposed to access his age, which just might stop us.

结论

截至ES2017,仍然没有完美的方法来进行私有财产交易。各种方法各有利弊。范围变量是真正的私有变量;有作用域的WeakMap是非常私有的,比有作用域的变量更实用;范围符号是合理的私有和合理的实用性;下划线通常足够私密且非常实用。


7
第一个示例代码段(“作用域变量”)是整体反模式-每个返回的对象将具有不同的类。不要那样做 如果要使用特权方法,请在构造函数中创建它们。
Bergi 2015年

1
将类包装在函数中似乎首先破坏了使用类的全部目的。如果您已经使用该函数创建实例,则最好将所有私有/公共成员也放置在该函数中,而不必理会整个class关键字。
Kokodoko

2
@Bergi @Kokodoko我编辑了作用域变量方法,使其速度稍快且不会中断instanceof。我承认,我认为这种方法仅出于完整性考虑而包括在内,应该更多地考虑其实际能力。
tristan

1
极好的解释!我仍然感到惊讶的是,ES6实际上使仿真私有变量变得更加困难,在ES5中,您只能在函数中使用var和this来仿真私有和公共变量。
Kokodoko

2
@Kokodoko如果您无需使用类,而只是将所有内容放入函数中,则还必须恢复为使用原型方法实现继承。到目前为止,在类上使用扩展是一种更干净的方法,因此在函数内部使用类是完全可以接受的。
AndroidDev '17

117

更新:语法更好提案正在实施中。欢迎捐款。


是的,有-在对象范围的访问- ES6介绍Symbol小号

符号是唯一的,除了反射(例如Java / C#中的private)外,您无法从外部访问任何符号,但是内部有权访问符号的任何人都可以将其用于键访问:

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

6
你不能用Object.getOwnPropertySymbols吗?;)
澳洲航空94年

41
@BenjaminGruenbaum:显然,符号不再确保真正的隐私:stackoverflow.com/a/22280202/1282216
d13

28
@trusktr通过三键?不,通过符号?是。非常类似于如何在C#和Java等语言中使用反射来访问私有字段。访问修饰符与安全性无关,它们与意图的明确性有关。
Benjamin Gruenbaum 2014年

9
似乎使用Symbols类似于这样做const myPrivateMethod = Math.random(); Something.prototype[''+myPrivateMethod] = function () { ... } new Something()[''+myPrivateMethod]();。从传统JavaScript的角度来看,这并不是真正的隐私,而是默默无闻。我认为“私有” JavaScript意味着使用闭包来封装变量。因此,这些变量无法通过反射访问。
trusktr 2014年

13
另外,我觉得使用privateand protected关键字比Symbolor 干净得多Name。我更喜欢点号而不是方括号。我想继续在私人物品上使用圆点。this.privateVar
trusktr 2014年

33

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

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


6
-1,这确实不能回答您的问题。(您也可以在ES5中对IIFE使用闭包)。通过反映大多数语言(Java,C#等),可以枚举私有属性。私有财产的目的是将意图传达给其他程序设计者,而不是强制执行安全性。
本杰明·格林巴姆2014年

1
@BenjaminGruenbaum,我知道,我希望我有一个更好的答案,我也不满意。
d13

我认为符号仍然是在编程环境中获取无法访问的成员的有效方法。是的,如果您确实想要它们,仍然可以找到它们,但这不是重点吗?您不应该在其中存储敏感信息,但是无论如何都不要在客户端代码中这样做。但这是为了从外部类隐藏属性或方法而工作。
Kokodoko

使用在模块级别作用域的变量代替类中的私有属性将导致单例行为或与statitc属性相似的行为。vars的实例将被共享。
阿德里安·莫伊萨

30

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

3
建议,通过将一个对象用作值,可以避免每个属性使用一个弱映射。这样,您还可以将get每种方法的地图数量减少到一个(例如const _ = privates.get(this); console.log(_.privateProp1);)。
昆汀·罗伊

是的,那也是完全可以选择的。我之所以这么做,是因为它更直接地映射到用户在使用真实属性时所编写的内容。
loganfsmyth

@loganfsmyth const myObj = new SomeClass(); console.log(privateProp1.get(myObj)) // "I am Private1"意味着您的财产是私有的还是没有私有的?
Barbu Barbu

2
为此,访问属性的代码将需要访问WeakMap对象,该对象通常在模块内部范围内且不可访问
loganfsmyth

22

为了将来供其他参考者参考,我现在听到的建议是使用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}`)
      }
    }
  }
}

20
请注意,这些属性是静态的。
Michael Theriot 2015年

8
我没有拒绝您的投票,但是您的weakmap示例完全错误。
本杰明·格伦鲍姆

4
即-您是在所有类实例之间共享数据,而不是在每个实例之间共享数据-我至少可以修复它吗?
Benjamin Gruenbaum

1
实际上,弱映射需要附加到给定实例。有关示例,请参见fitzgeraldnick.com/weblog/53
结婚

2
根据MDN,不允许将原始数据类型(例如Symbols)用作WeakMap密钥。MDN WeakMap文档
leepowell

12

取决于你问的人 :-)

似乎已纳入当前草案private的“ 最大最小类”提议中未包含任何属性修饰符。

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


3
这是非常不可能的,私人的名字将它做成ES6,虽然他们是某种形式的对ES7私人事情的想法。
澳洲航空94年重型

根据我的理解,私有符号和唯一字符串值都已被Symbols取代。
Benjamin Gruenbaum 2014年

是的,它可能会变成符号。但是,规范中当前包含的“符号”仅用于描述[[prototype]]之类的内部属性,无法在用户代码中创建和使用它们。你知道一些文档吗?
Bergi 2014年

我刚刚意识到模块可以用来设置隐私。结合使用符号,您可能只需要...?
d13 2014年

1
@Cody:您的整个模块代码在ES6中确实具有其自己的作用域,无需IEFE。是的,符号的目的是唯一性(避免冲突),而不是隐私。
Bergi

10

使用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一样,这些私有属性是静态的,因此在范围上是全局的。与Singletons一起使用时,它们会很好地工作,但必须注意瞬态对象。扩展上面的示例:

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

4
private static
Danyal Aytekin

@DanyalAytekin:很好。这些私有属性是静态的,因此在范围上是全局的。我已经更新了答案以反映这一点。
Johnny Oshika

我对函数式编程(尤其是Elm和Haskell)了解得越多,我相信JS程序员将受益于基于模块的“模块化”方法,而不是基于OOP类的方法。如果我们将ES6模块视为构建应用程序的基础,而完全忘记了类,那么我相信我们最终可能会获得总体上更好的应用程序。有经验的Elm或Haskell用户可以对这种方法发表评论吗?
d13 2016年

1
在更新中,第二个a.say(); // a应该是b.say(); // b
grokky

尝试过的let _message = null方式,不是很酷,当多次调用构造函数时,它会变得混乱。
Littlee

9

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

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

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

我能想到的好处:

  • 我们可以有私有方法(_greet_updateMessage只要我们不采取行动,像私有方法export引用)
  • 尽管它们不在原型上,但上述方法将节省内存,因为实例是在类外部创建一次的(与在构造函数中定义它们相反)
  • 我们不会泄漏任何全局变量,因为我们位于模块内部
  • 我们还可以使用绑定_privates对象来拥有私有属性

我能想到的一些缺点:

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


8

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

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

1使用课程词创建课程

2在其构造函数内部,使用let OR const保留字声明块范围变量->由于它们是块范围,因此无法从外部访问(封装)

3要允许对这些变量的某些访问控制(设置器|获取器),可以使用以下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

1
尽管到目前为止,对于类的每个实例都重新声明了构造函数中声明的所有方法,但这是(目前)解决此问题的唯一方法。关于性能和内存使用情况,这是一个坏主意。类方法应在构造函数范围之外声明。
Freezystem

@Freezystem首先:首先,这些是实例方法(不是Class方法)。OP的第二个问题是:_我如何防止访问instance.property?_,我的答案是:一个示例…… 第三,如果您有更好的主意-让我们听听吧
Nikita Kurtin,

1
我并不是说您错了,而是说您的解决方案是实现私有变量的最佳折衷方案,尽管每次调用时都会创建每个实例方法的副本,new Something();因为在构造函数中声明了您的方法可以访问这些实例方法私有变量。如果您创建了很多类的实例,则可能会导致大量内存消耗,从而导致性能问题。方法应该在构造函数范围之外声明。我的评论更多是对解决方案缺陷的解释,而不是批评。
Freezystem

1
但是在构造函数中定义整个类不是不好的做法吗?我们不是现在就“黑客” JavaScript吗?只要看看其他任何OOP编程语言,您就会发现构造函数并不意味着要定义一个类。
Kokodoko

1
是的,这就是我的意思,您的解决方案有效!我只是说总体上,我很惊讶ES6添加了一个“类”关键字,但是删除了使用var和this来实现封装的优雅解决方案。
Kokodoko

8

另一种“私有”方法

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

自动完成,仅显示公开内容


1
问题是,我们不希望通过编辑器访问私有变量,我们也不想保护私有变量不受外部影响,也就是说,公共/私有行为。如果您的代码完成了,则可以从类外部访问(并且重要的思想:重写)。您的@private评论不能阻止这些,它只是用于生成文档的功能,而是您的IDE。
Adrian Preuss '18

是的,我知道这一点。仅仅对我来说就足够了,也许对其他人也足够了。我知道这并没有真正将我的变量设为私有;这只是警告我不要尝试从外部访问它(当然,仅当我和我的团队都使用支持此功能的IDE时)。Javascript(和其他语言,如Python)在设计时并未考虑访问级别。人们做了各种各样的事情来以某种方式实现该功能,但最终我们只能通过破解该语言来实现该功能。如果可以的话,我决定采用一种更“自然”的方法。
Lucio Paiva '18

6

弱地图

  • IE11中支持(不包含符号)
  • 硬私有(由于导致使用符号的道具是软私有Object.getOwnPropertySymbols
  • 看起来很干净(不同于需要构造函数中所有prop和方法的闭包)

首先,定义一个包装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,但在示例中看起来更干净。


6

哦,这么多奇特的解决方案!我平时不关心隐私,所以我用“伪隐私”,因为它的在这里说。但是,如果要小心(如果对此有一些特殊要求),则在此示例中使用类似的内容:

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

5

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

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

Private.js

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

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

评论将不胜感激。


一般来说,我喜欢这种方法。反馈:1.每个类都需要一个不同的private.js模块,以防止发生冲突。2.我不喜欢通过内联定义每个私有方法来使构造函数变长的潜力。3.如果所有的类方法都在一个文件中,那就太好了。
Doug Coburn

5

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

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

4

我相信使用构造函数内部的闭包可以“两全其美”。有两种变体:

所有数据成员都是私有的

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

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

class Test {
   constructor() {

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

一些成员是私人的

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

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

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

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

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


4

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

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

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

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

  return Human;
})();

const bob = new Human('Bob');

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


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

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

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


谢谢,我将尝试符号:)从以上所有答案中,这似乎是创建无法访问的类成员的最直接方法:)
Kokodoko

4

甚至打字稿也做不到。从他们的文档中

当成员被标​​记为私有时,无法从其包含的类之外访问它。例如:

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

因此,他们的“私人”关键字无效。


2
嗯,它仍然有效,因为它可以防止在IDE中进行“不良”编程。它显示了您应该和不应该使用的成员。我认为这是使用私人和公共场所的主要原因。(例如,当您将C#编译为机器代码时,private仍然是private吗?谁知道?)。阅读其他答案时,似乎使用@Symbol也会使成员无法访问。但是,即使仍然可以从控制台找到符号。
Kokodoko

在将TypeScript转换为JavaScript期间是否发生TypeScript错误?(就像类型检查发生在转换时一样。而不是某些运行时私有机制。)
Eljay

4

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

有一个示例说明我如何在此Codepen中使用私有方法。在下面的代码段中,Subscribable类具有两个“私有”功能processprocessCallbacks。可以以这种方式添加任何属性,并通过使用闭包将它们保留为私有。如果关注点被很好地分隔开,并且在闭包完成任务时无需添加更多语法,那么Javascript就不会变得肿,这是IMO隐私的罕见需求。

const Subscribable = (function(){

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

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

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

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

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

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

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


4

我认为,本杰明的答案在大多数情况下可能是最好的,除非该语言本地支持显式私有变量。

但是,如果出于某种原因您需要使用阻止访问Object.getOwnPropertySymbols(),则我考虑使用的一种方法是将唯一的,不可配置的,不可枚举的,不可写的属性附加为构造中每个对象的属性标识符(例如unique 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"

如果性能成为问题,则与使用a相比,此方法的潜在优势WeakMap访问时间更快


1
如果我错了,请纠正我,但是此代码不会包含内存泄漏,因为即使对象已被销毁,privateVars仍将存储对象的私有变量?
拉塞尔·桑托斯

@RussellSantos,您是正确的,假设某些时候需要对这些对象进行垃圾收集。感谢您指出这一点。在我的示例中,我添加了一个destroy()方法,每当需要删除一个对象时,使用代码应调用该方法。
NanoWizard

4

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

class Clazz {
  constructor() {
    var _level = 1

    function _private(x) {
      return _level * x;
    }
    return {
      level: _level,
      public: this.private,
      public2: function(x) {
        return _private(x);
      },
      public3: function(x) {
        return _private(x) * this.public(x);
      },
    };
  }

  private(x) {
    return x * x;
  }
}

var clazz = new Clazz();

console.log(clazz._level); //undefined
console.log(clazz._private); // undefined
console.log(clazz.level); // 1
console.log(clazz.public(1)); //1
console.log(clazz.public2(2)); //2
console.log(clazz.public3(3)); //27
console.log(clazz.private(0)); //error


3
class Something {
  constructor(){
    var _property = "test";
    Object.defineProperty(this, "property", {
        get: function(){ return _property}
    });
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"
instance.property = "can read from outside, but can't write";
console.log(instance.property); //=> "test"

2
最好避免仅提供代码答案。如果您能解释代码如何回答OP的问题,那就更好了
Stewart_R

实际上,这是使只读变量比私有变量更多的方法。私有变量不应被外部访问。 console.log(instance.property)应该抛出或给您未定义的内容,而不是给您“测试”。
oooyaya

3

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

class Example {
  constructor(foo) {

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

    // public interface
    return self.public;
  }

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

  // everything else private
  bar = 10
}

const test = new Example('FOO');
console.log(test.foo); // undefined
console.log(test.noop); // { data: [] }
console.log(test.bar); // undefined

2

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

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

我称这种方法为访问器模式。基本思想是,我们有一个闭包,闭包内有一个,并且我们创建了一个私有对象(在构造函数中),只有拥有key时才能访问该对象。

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


问题是关于如何在ES6类中实现这一点。
Michael Franzl

您可以在ES6类中使用完全相同的方法。ES6类主要是在我在示例中介绍的功能之上的糖。原始张贴者很有可能使用的是Transpiler,在这种情况下,WeakMap或Symbols仍然需要使用polyfills。我的答案是有效的。
guitarino

2

请参阅此答案以获取一个干净,简单的“类”解决方案,该解决方案具有私有和公共接口并支持合成


2

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

2

我使用这种模式,并且对我一直有效

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

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

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

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

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



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


2

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

class Names {
  constructor() {
    this.privateProperty = 'John';
    return _public(this, arguments);
  }
  privateMethod() { }
}

const names = new Names(1,2,3);
console.log(names.somePublicMethod); //[Function]
console.log(names.publicProperty); //'Jasmine'
console.log(names.privateMethod); //undefined
console.log(names.privateProperty); //undefind

function _public(_this, _arguments) {
  class Names {
    constructor() {
      this.publicProperty = 'Jasmine';
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

    somePublicMethod() {
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

  }
  return new Names(..._arguments);
}

2

您可以尝试此https://www.npmjs.com/package/private-members

该程序包将按实例保存成员。

const pvt = require('private-members');
const _ = pvt();

let Exemplo = (function () {    
    function Exemplo() {
        _(this).msg = "Minha Mensagem";
    }

    _().mensagem = function() {
        return _(this).msg;
    }

    Exemplo.prototype.showMsg = function () {
        let msg = _(this).mensagem();
        console.log(msg);
    };

    return Exemplo;
})();

module.exports = Exemplo;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.