JavaScript ES6 类中的私有属性

IT技术 javascript class private-members ecmascript-6
2021-01-21 07:23:14

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

这是一个例子。我怎样才能阻止访问instance.property

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

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

简短的回答,不,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; }
}
所有这些都引入了间接性。现在如何将getNamesetName属性设为私有?
2021-03-16 07:23:14
@aij 所以请说出一种不同的语言。你可以很容易地看到,他可以只注释掉 setter 或 getter 或两者,这_name确实是私密的。
2021-03-16 07:23:14
这太奇怪了!在 ES6 中,您将创建比 ES6 之前更多的“闭合金字塔”!在构造函数中定义函数看起来比在上面的 ES5 示例中更难看。
2021-03-22 07:23:14
我最喜欢这个解决方案。我同意它不应该用于缩放,但它非常适合每个包含通常只实例化一次的类。
2021-03-26 07:23:14
此外,每次创建新组件时,您都会重新定义此类的每个组件。
2021-03-29 07:23:14

私有类功能处于第 3 阶段提案中所有主要浏览器支持其大部分功能

class Something {
  #property;

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

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

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

const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> test
console.log(instance.#property); //=> Syntax error
你如何用这个编写私有方法我可以这样做:#beep() {}; 而这个:async #bzzzt() {}
2021-03-12 07:23:14
哇这太丑了。Hashtag 是有效字符。该财产不是真正私人的,还是?.. 我在 TypeScript 中检查过。私有成员不是以私有或只读方式(从外部)编译的。刚刚声明为另一个(公共)财产。(ES5)。
2021-03-13 07:23:14
目前,Google Chrome 和 Node.js v12 也正式支持该解决方案。私有 getter 和 setter 正在开发中。
2021-03-17 07:23:14
那么 eslint 呢?我在等号处遇到解析器错误。Babel 正在工作,只是 eslint 无法解析这个新的 js 语法。
2021-04-03 07:23:14
使用_将是一个重大变化,除非您的意思是 JS 根本不需要私有私有属性
2021-04-03 07:23:14

扩展@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 可用于避免先前方法的性能和内存损失。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除非您非常小心,否则会造成内存泄漏,为此,两者没有其他区别。)

答案不全:作用域符号

Symbol 是一种可以用作属性名称的原始值。您可以使用作用域变量方法创建一个私有 Symbol,然后将私有数据存储在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 非常私有,比作用域变量更实用;作用域符号是相当私密且相当实用的;下划线通常足够私密且非常实用。

第一个示例代码段(“作用域变量”)是一个完整的反模式——每个返回的对象都有一个不同的类。不要那样做。如果您想要特权方法,请在构造函数中创建它们。
2021-03-16 07:23:14
很好的解释!我仍然感到惊讶的是,ES6 实际上让模拟私有变量变得更加困难,而在 ES5 中,您可以只在函数中使用 var 和 this 来模拟私有和公共变量。
2021-03-25 07:23:14
@Bergi @Kokodoko 我编辑了作用域变量方法,使其稍微快一点而不是 break instanceof我承认我认为这种方法只是为了完整性而包括在内,并且应该更多地考虑它的实际能力。
2021-04-03 07:23:14
将一个类包装在一个函数中似乎一开始就违背了使用类的全部目的。如果您已经使用该函数创建了一个实例,那么您也可以将所有私有/公共成员也放在该函数中,而忘记整个 class 关键字。
2021-04-07 07:23:14
@Kokodoko如果你省去了这个类而只是把所有的东西都放在函数中,你还必须恢复使用原型方法实现继承。到目前为止,在类上使用扩展是一种更简洁的方法,因此在函数内使用类是完全可以接受的。
2021-04-09 07:23:14

更新:一个具有更好语法提案正在酝酿中欢迎投稿。


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

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

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

var instance = new Something();

console.log(instance.property); //=> undefined, can only access with access to the Symbol
@trusktr 通过三个键?不。通过符号?是的。非常像如何在 C# 和 Java 等语言中使用反射来访问私有字段。访问修饰符与安全无关 - 它们与意图的清晰度有关。
2021-03-19 07:23:14
你不能用Object.getOwnPropertySymbols吗?;)
2021-03-24 07:23:14
另外,我觉得使用privateandprotected关键字会比Symbolor干净得多Name我更喜欢点符号而不是括号符号。我想继续在私人事物上使用点。this.privateVar
2021-03-25 07:23:14
@BenjaminGruenbaum:显然符号不再确保真正的隐私:stackoverflow.com/a/22280202/1282216
2021-04-02 07:23:14
使用 Symbols 似乎类似于做const myPrivateMethod = Math.random(); Something.prototype[''+myPrivateMethod] = function () { ... } new Something()[''+myPrivateMethod]();。这并不是真正的隐私,而是传统 JavaScript 意义上的默默无闻。我认为“私有”JavaScript 意味着使用闭包来封装变量。因此,无法通过反射访问这些变量。
2021-04-03 07:23:14

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

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

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