【问题标题】:Private properties in JavaScript ES6 classesJavaScript ES6 类中的私有属性
【发布时间】:2014-04-05 01:34:30
【问题描述】:

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

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

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

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

【问题讨论】:

标签: javascript class private-members ecmascript-6


【解决方案1】:

简短的回答,不,没有对带有 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; }
}

【讨论】:

  • 我最喜欢这个解决方案。我同意它不应该用于缩放,但它非常适合每个包含通常只实例化一次的类。
  • 此外,每次创建新组件时,您都会重新定义此类的每个组件。
  • 这太奇怪了!在 ES6 中,您正在创建比 ES6 之前更多的“闭合金字塔”!在构造函数中定义函数看起来比在上面的 ES5 示例中更难看。
  • 所有这些都是引入间接。现在如何将getNamesetName 属性设为私有?
  • @aij 所以请说出一种不同的语言。您可以很容易地看到,他可以只注释掉 setter 或 getter 或两者兼而有之,并且 _name 是真正私有的。
【解决方案2】:

Private class featuresStage 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

【讨论】:

  • 那么 eslint 呢?我在等号处遇到解析器错误。 Babel 可以工作了,只是 eslint 无法解析这个新的 js 语法。
  • 哇,这太丑了。 Hashtag 是一个有效的字符。该财产不是真正的私人财产,或者? .. 我在 TypeScript 中检查了它。私有成员不是以私有或只读(从外部)编译的。刚刚宣布为另一个(公共)财产。 (ES5)。
  • By now,Google Chrome 和 Node.js v12 也正式支持该方案。私有 getter 和 setter 正在开发中。
  • 你如何用这个编写私有方法?我可以这样做吗:#beep() {};还有这个:async #bzzzt() {}?
  • 使用_ 将是一个重大变化,除非您的意思是JS 根本不需要private 私有属性
【解决方案3】:

扩展@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

作用域弱映射

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

例子:

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

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

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

  return Person;
})();

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

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

这个例子使用一个Object来为多个私有属性使用一个WeakMap;您还可以使用多个 WeakMap 并像 age.set(this, 20) 一样使用它们,或者编写一个小包装器并以其他方式使用它,例如 privateProps.set(this, 'age', 0)

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

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

半答案:作用域符号

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

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

例子:

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

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

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

  return Person;
})();

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

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

半答案:下划线

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

例子:

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

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

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

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

结论

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

【讨论】:

  • 第一个示例 sn-p(“作用域变量”)是一个完全的反模式——每个返回的对象都有一个不同的类。不要那样做。如果您想要特权方法,请在构造函数中创建它们。
  • 在函数中包装一个类似乎首先违背了使用类的全部目的。如果您已经使用该函数来创建实例,那么您不妨将所有私有/公共成员也放在该函数中,而忘记整个类关键字。
  • @Bergi @Kokodoko 我编辑了作用域变量方法,使其稍微快一些并且不会破坏instanceof。我承认我认为这种方法只是为了完整起见,应该更多地考虑它的实际能力。
  • 很好的解释!我仍然很惊讶 ES6 实际上使模拟私有变量变得更加困难,而在 ES5 中,您可以在函数中使用 var 和 this 来模拟私有和公共。
  • @Kokodoko 如果你放弃类而只是把所有东西都放在函数中,你还必须恢复使用原型方法来实现继承。迄今为止,在类上使用扩展是一种更简洁的方法,因此在函数中使用类是完全可以接受的。
【解决方案4】:

更新:proposal with nicer syntax 即将到来。欢迎投稿。


是的,有 - 用于对象中的范围访问 - ES6 introduces Symbols

符号是独一无二的,除了反射(如 Java/C# 中的私有),您无法从外部访问符号,但任何有权访问内部符号的人都可以使用它进行密钥访问:

var property = Symbol();
class Something {
    constructor(){
        this[property] = "test";
    }
}

var instance = new Something();

console.log(instance.property); //=> undefined, can only access with access to the Symbol

【讨论】:

  • 你不能用Object.getOwnPropertySymbols吗? ;)
  • @BenjaminGruenbaum:显然符号不再确保真正的隐私:stackoverflow.com/a/22280202/1282216
  • @trusktr 通过三个键?不,通过符号?是的。非常类似于如何在 C# 和 Java 等语言中使用反射来访问私有字段。访问修饰符与安全性无关 - 它们与意图的清晰性有关。
  • 似乎使用符号类似于使用const myPrivateMethod = Math.random(); Something.prototype[''+myPrivateMethod] = function () { ... } new Something()[''+myPrivateMethod]();。这不是真正的隐私,它是传统 JavaScript 意义上的晦涩难懂。我认为“私有”JavaScript 意味着使用闭包来封装变量。因此,这些变量无法通过反射访问。
  • 另外,我觉得使用privateprotected 关键字会比SymbolName 干净得多。我更喜欢点符号而不是括号符号。我想继续使用点来处理私人事务。 this.privateVar
【解决方案5】:

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

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

【讨论】:

  • -1,这并不能真正回答您的问题。 (你也可以在 ES5 中使用带有 IIFE 的闭包)。在大多数语言(Java、C# 等)中,私有属性都可以通过反射来枚举。私有属性的目的是向其他程序员传达意图,而不是强制执行安全性。
  • @BenjaminGruenbaum,我知道,我希望我有一个更好的答案,我也不满意。
  • 我认为符号仍然是在编程环境中实现不可访问成员的有效方法。是的,如果你真的想要,它们仍然可以找到,但这不是重点,不是吗?你不应该在其中存储敏感信息,但无论如何你都不应该在客户端代码中这样做。但它的作用是向外部类隐藏属性或方法。
  • 使用模块级别的变量代替类中的私有属性将导致单例行为或类似于统计属性的行为。变量的实例将被共享。
【解决方案6】:

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

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    privateProp1.set(this, "I am Private1");
    privateProp2.set(this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(privateProp1.get(this), privateProp2.get(this))
    };        
  }

  printPrivate() {
    console.log(privateProp1.get(this));
  }
}

显然,这可能很慢,而且绝对丑陋,但它确实提供了隐私。

请记住,即使这样也不是完美的,因为 Javascript 是如此动态。还是有人可以做的

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

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

const {set: WMSet, get: WMGet} = WeakMap.prototype;

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    WMSet.call(privateProp1, this, "I am Private1");
    WMSet.call(privateProp2, this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(WMGet.call(privateProp1, this), WMGet.call(privateProp2, this))
    };        
  }

  printPrivate() {
    console.log(WMGet.call(privateProp1, this));
  }
}

【讨论】:

  • 作为建议,您可以通过使用对象作为值来避免每个属性使用一个弱映射。这样,您还可以将地图的get 数量减少到每个方法一个(例如const _ = privates.get(this); console.log(_.privateProp1);)。
  • 是的,这也完全是一种选择。我主要是这样做的,因为它更直接地映射到用户在使用真实属性时会写的内容。
  • @loganfsmyth const myObj = new SomeClass(); console.log(privateProp1.get(myObj)) // "I am Private1" 这意味着您的财产是否是私有的?
  • 为此,访问该属性的代码需要访问 WeakMap 对象,该对象通常位于模块内部且不可访问
【解决方案7】:

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

【讨论】:

  • 请注意这些属性是静态的。
  • 我没有对你投反对票,但你的弱图示例完全错误。
  • 即 - 您在所有类实例之间而不是每个实例之间共享数据 - 我至少可以修复它吗?
  • 确实,weakmap 需要附加到给定的实例。有关示例,请参见 fitzgeraldnick.com/weblog/53
  • 根据 MDN,Symbols 等原始数据类型不允许作为 WeakMap 键。 MDN WeakMap Documentation
【解决方案8】:

取决于whom you ask :-)

Maximally minimal classes proposal 中不包含 private 属性修饰符,这似乎已进入 current draft

但是,可能有 support for private names,它确实允许私有属性 - 它们可能也可以在类定义中使用。

【讨论】:

  • 私有名称非常不太可能进入 ES6,尽管他们正在为 ES7 考虑某种形式的私有名称。
  • @Qantas94Heavy 私有名称和唯一字符串值都已被符号所取代。
  • 是的,它可能会变成符号。但是,afaik 目前包含在规范中的“符号”仅用于描述 [[prototype]] 等内部属性,无法在用户代码中创建和使用它们。你知道一些文档吗?
  • 我刚刚意识到可以使用模块来设置隐私。结合符号可能就是您所需要的一切......?
  • @Cody:你的整个 module 代码在 ES6 中确实有自己的范围,不需要 IEFE。是的,符号的目的是为了唯一性(避免碰撞),而不是隐私。
【解决方案9】:

使用 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 在 cmets 中概述的那样,这些私有属性是静态的,因此在范围内是全局的。它们在使用 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
  • @DanyalAytekin:这是一个很好的观点。这些私有属性是静态的,因此在范围内是全局的。我已经更新了我的答案以反映这一点。
  • 我对函数式编程(尤其是 Elm 和 Haskell)了解得越多,我就越相信 JS 程序员将受益于基于模块的“模块化”方法,而不是基于 OOP 类的方法。如果我们将 ES6 模块视为构建应用程序的基础,而完全忘记类,我相信我们最终可能会得到更好的应用程序。任何有经验的 Elm 或 Haskell 用户可以评论这种方法吗?
  • 更新中第二个a.say(); // a应该是b.say(); // b
  • 试过let _message = null的方式,不太酷,当多次调用构造函数时,它搞砸了。
【解决方案10】:

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

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

1 使用class word 创建类

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

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

【讨论】:

  • 这是(目前)唯一解决此问题的方法,尽管构造函数中声明的所有方法都为类的每个实例重新声明。关于性能和内存使用,这是一个非常糟糕的主意。类方法应该在构造函数范围之外声明。
  • @Freezystem First:First那些是实例方法(不是类方法)。 第二个 OP 问题是:_如何防止访问 instance.property?_ 我的答案是:如何... 第三个 如果您有更好的想法 - 让我们来听听
  • 我并不是说你错了,我说你的解决方案是实现私有变量的最佳折衷方案,尽管每次调用 new Something(); 时都会创建每个实例方法的副本,因为你的在构造函数中声明方法可以访问这些私有变量。如果您创建大量类的实例,这可能会导致大量内存消耗,从而导致性能问题。方法应该在构造函数范围之外声明。我的评论更多是对您的解决方案缺陷的解释,而不是批评。
  • 但是在构造函数中定义整个类不是不好的做法吗?我们现在不只是“破解”javascript吗?只要看看任何其他 OOP 编程语言,你就会发现构造函数并不是用来定义一个类的。
  • 是的,这就是我的意思,您的解决方案有效!我只是说,总的来说,我很惊讶 ES6 添加了一个 'class' 关键字,但删除了使用 var 和 this 实现封装的优雅解决方案。
【解决方案11】:

完成@d13 和@johnny-oshika 和@DanyalAytekin 的cmets:

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

ma​​in.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 对象来拥有私有属性

我能想到的一些缺点:

可以在此处找到正在运行的 sn-p:http://www.webpackbin.com/NJgI5J8lZ

【讨论】:

    【解决方案12】:

    “私人”的不同方法

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

    【讨论】:

    • 问题是,我们不想通过编辑器访问私有变量,我们不想保护私有变量不受外部影响——也就是说,公共/私有做什么.如果您的代码已完成,您可以从类外部访问(重要的是:override)这些变量。您的 @private 评论无法阻止这些,它只是文档生成的功能,您是 IDE。
    • 是的,我知道这一点。只是这对我来说已经足够了,对于其他人来说可能已经足够了。我知道这并没有真正将我的变量设为私有。它只是警告我不要尝试从外部访问它(当然,如果我和我的团队都使用支持此功能的 IDE)。 Javascript(和其他语言,如 Python)在设计时并未考虑访问级别。人们做了各种各样的事情来以某种方式实现该功能,但最终我们只是破解了语言来实现这一点。如果您愿意,我决定采用更“自然”的方法。
    【解决方案13】:

    哦,这么多奇特的解决方案!我通常不关心隐私,所以我使用 “伪隐私”,因为它是 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)
    }
    

    【讨论】:

      【解决方案14】:

      弱地图

      • 在 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,但在示例中看起来更简洁。

      【讨论】:

        【解决方案15】:

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

        我根据在线书籍“探索 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 就不太好。

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

        四种基本模式是:

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

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

        【讨论】:

          【解决方案16】:

          我个人喜欢bind operator:: 的建议,然后将其与@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
          

          cmets 将不胜感激。

          【讨论】:

          • 一般来说我喜欢这种方法。反馈: 1. 您需要为每个类使用不同的 private.js 模块以防止冲突。 2. 我不喜欢通过内联定义每个私有方法来使构造函数变得很长的潜力。 3. 如果所有的类方法都在一个文件中就好了。
          【解决方案17】:

          我认为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

          【讨论】:

          • 如果我错了,请纠正我,但是这段代码不会包含内存泄漏,因为即使对象已经被销毁,privateVars 仍会存储对象的私有变量?
          • @RussellSantos 你是对的,假设在某些时候需要对对象进行垃圾收集。谢谢你指出这一点。在我的示例中,我添加了一个 destroy() 方法,只要需要删除对象,就应该使用代码调用该方法。
          【解决方案18】:

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

          所有数据成员都是私有的

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

          【讨论】:

            【解决方案19】:

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

            【讨论】:

              【解决方案20】:

              即使是 Typescript 也做不到。来自他们的documentation

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

              class Animal {
                  private name: string;
                  constructor(theName: string) { this.name = theName; }
              }
              
              new Animal("Cat").name; // Error: 'name' is private;
              

              但是在他们的playground 上转译了:

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

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

              【讨论】:

              • 嗯,它仍然有效,因为它可以防止在 IDE 中出现“不良”编程。它向您显示您应该和不应该使用哪些成员。我认为这是使用私有和公共的主要原因。 (例如,当你将 C# 编译为机器码时,private 仍然是私有的吗?谁知道呢?)。在阅读其他答案时,似乎使用 @Symbol 也可以使成员无法访问。但即使是符号仍然可以从控制台找到。
              • TypeScript 转 JavaScript 时是否出现 TypeScript 错误? (就像类型检查发生在转瞬即逝的时间。而不是一些运行时私有机制。)
              • 请注意,此响应讨论的是 TypeScript 特定的私有 修饰符,但 TypeScript 4.3 引入了对 ECMAScript #private Class Elements 的额外支持,与私有修饰符不同,它不仅在编译时是私有的,但也在运行时(如果编译为 >= ES 2015)。我创建了sample code in TypeScript playfeild 来证明它是真正私有的(ESNext 目标与 Chrome 一起工作,实现了对 ES 私有字段提案的支持)
              【解决方案21】:

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

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

              const Subscribable = (function(){
              
                const process = (self, eventName, args) => {
                  self.processing.set(eventName, setTimeout(() => processCallbacks(self, eventName, args)))};
              
                const processCallbacks = (self, eventName, args) => {
                  if (self.callingBack.get(eventName).length > 0){
                    const [nextCallback, ...callingBack] = self.callingBack.get(eventName);
                    self.callingBack.set(eventName, callingBack);
                    process(self, eventName, args);
                    nextCallback(...args)}
                  else {
                    delete self.processing.delete(eventName)}};
              
                return class {
                  constructor(){
                    this.callingBack = new Map();
                    this.processing = new Map();
                    this.toCallbacks = new Map()}
              
                  subscribe(eventName, callback){
                    const callbacks = this.unsubscribe(eventName, callback);
                    this.toCallbacks.set(eventName,  [...callbacks, callback]);
                    return () => this.unsubscribe(eventName, callback)}  // callable to unsubscribe for convenience
              
                  unsubscribe(eventName, callback){
                    let callbacks = this.toCallbacks.get(eventName) || [];
                    callbacks = callbacks.filter(subscribedCallback => subscribedCallback !== callback);
                    if (callbacks.length > 0) {
                      this.toCallbacks.set(eventName, callbacks)}
                    else {
                      this.toCallbacks.delete(eventName)}
                    return callbacks}
              
                  emit(eventName, ...args){
                    this.callingBack.set(eventName, this.toCallbacks.get(eventName) || []);
                    if (!this.processing.has(eventName)){
                      process(this, eventName, args)}}}})();
              

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

              【讨论】:

                【解决方案22】:

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

                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

                【讨论】:

                  【解决方案23】:
                  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 的问题会更好
                  • 这真的是如何使只读变量多于私有变量。私有变量不应该被外部访问。 console.log(instance.property) 应该抛出或给你未定义,而不是给你回“测试”。
                  【解决方案24】:

                  另一种方式类似于最后两个贴

                  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
                  

                  【讨论】:

                    【解决方案25】:

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

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

                    https://codesandbox.io/s/class-demo-837bj

                    class Animal {
                        static count = 0 // class static public
                        static #ClassPriVar = 3 // class static private
                    
                        constructor(kind) {
                            this.kind = kind // instance public property
                            Animal.count++
                            let InstancePriVar = 'InstancePriVar: ' + kind // instance private constructor-var
                            log(InstancePriVar)
                            Animal.#ClassPriVar += 3
                            this.adhoc = 'adhoc' // instance public property w/out constructor- parameter
                        }
                    
                        #PawCount = 4 // instance private var
                    
                        set Paws(newPawCount) {
                            // instance public prop
                            this.#PawCount = newPawCount
                        }
                    
                        get Paws() {
                            // instance public prop
                            return this.#PawCount
                        }
                    
                        get GetPriVar() {
                            // instance public prop
                            return Animal.#ClassPriVar
                        }
                    
                        static get GetPriVarStat() {
                            // class public prop
                            return Animal.#ClassPriVar
                        }
                    
                        PrintKind() {
                            // instance public method
                            log('kind: ' + this.kind)
                        }
                    
                        ReturnKind() {
                            // instance public function
                            return this.kind
                        }
                    
                        /* May be unsupported
                    
                        get #PrivMeth(){  // instance private prop
                            return Animal.#ClassPriVar + ' Private Method'
                        }
                    
                        static get #PrivMeth(){  // class private prop
                            return Animal.#ClassPriVar + ' Private Method'
                        }
                        */
                    }
                    
                    function log(str) {
                        console.log(str)
                    }
                    
                    // TESTING
                    
                    log(Animal.count) // static, avail w/out instance
                    log(Animal.GetPriVarStat) // static, avail w/out instance
                    
                    let A = new Animal('Cat')
                    log(Animal.count + ': ' + A.kind)
                    log(A.GetPriVar)
                    A.PrintKind()
                    A.Paws = 6
                    log('Paws: ' + A.Paws)
                    log('ReturnKind: ' + A.ReturnKind())
                    log(A.adhoc)
                    
                    let B = new Animal('Dog')
                    log(Animal.count + ': ' + B.kind)
                    log(B.GetPriVar)
                    log(A.GetPriVar) // returns same as B.GetPriVar. Acts like a class-level property, but called like an instance-level property. It's cuz non-stat fx requires instance.
                    
                    log('class: ' + Animal.GetPriVarStat)
                    
                    // undefined
                    log('instance: ' + B.GetPriVarStat) // static class fx
                    log(Animal.GetPriVar) // non-stat instance fx
                    log(A.InstancePriVar) // private
                    log(Animal.InstancePriVar) // private instance var
                    log('PawCount: ' + A.PawCount) // private. Use getter
                    /* log('PawCount: ' + A.#PawCount) // private. Use getter
                    log('PawCount: ' + Animal.#PawCount) // Instance and private. Use getter */

                    【讨论】:

                      【解决方案27】:

                      看了前面的回答我觉得这个例子可以总结一下上面的解决方案

                      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

                      【讨论】:

                        【解决方案28】:

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

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

                        我将此方法称为访问器模式。基本思想是我们有一个闭包,闭包内部有一个key,并且我们创建了一个私有对象(在构造函数中),它可以仅当您拥有 key 时才能访问。

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

                        【讨论】:

                        • 问题是关于如何在 ES6 类中实现这一点。
                        • 您可以在 ES6 类中使用完全相同的方法。 ES6 类主要是我在示例中介绍的函数之上的糖。原始海报很可能使用了转译器,在这种情况下,WeakMaps 或 Symbols 仍然需要 polyfill。无论如何,我的回答都是有效的。
                        【解决方案29】:

                        请参阅this answer 以获得具有私有和公共接口并支持组合的干净简单的“类”解决方案

                        【讨论】:

                          【解决方案30】:

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

                          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

                          【讨论】:

                            猜你喜欢
                            • 2023-03-04
                            • 2016-04-24
                            • 2018-10-03
                            • 2018-07-21
                            • 2013-09-04
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多