ES6中的访问修饰符(私有、受保护)

12
注意:我已经阅读了以下关于符号,WeakMaps和Maps的SO问题和7个答案(截至目前),请在投票之前阅读完整个问题:JavaScript ES6类中的私有属性 文章:https://esdiscuss.org/topic/es7-property-initializers 下面是我的简单类,其中包含私有、公共和受保护的属性和方法。

  'use strict';
  class MyClass {
    constructor () {
      this.publicVar = 'This is Public Variable';
      this.privateVar = 'This is Private Variable';
      this.protectedVar = 'This is Protected Variable';
    } // Public Constructor Method.
    
    publicMethod () {
      console.log('   Accessing this.publicVar: ', this.publicVar);
      console.log('   Accessing this.privateVar: ', this.privateVar);
      console.log('   Accessing this.protectedVar: ', this.protectedVar);
      return 'Its Public Method'
    } // Public Method.

    privateMethod () {return 'Its Private Method'} // Private Method.
    protectedMethod () {return 'Its Protected Method'} // Protected Method.

    foo () {
      this.publicMethod();
      this.privateMethod();
      this.protectedMethod();
    } // Public Method
  } // end class

我正在实例化对象并调用公共方法,一切都按预期工作。

let MyObject = new MyClass;
MyObject.foo(); // Works fine.
console.log( MyObject.publicVar ); // Works
console.log( MyObject.publicMethod() ); // Works

按预期工作。

现在我的问题是,我知道像Symbol这样的东西在ES6规范中,那么在ES6类中,如何实现保护和私有变量/方法的当前解决方法。

console.log( MyObject.privateVar ); // Works
console.log( MyObject.privateMethod() ); // Works

我希望这个属性和方法只在它自己的类中可见。
console.log( MyObject.protectedVar ); // Works
console.log( MyObject.protectedMethod() ); // Works

我希望这个属性和方法在它自己的类中以及继承它的类中可见。
欢迎提供解决方案以实现此行为。

4
如果您已经阅读了关于符号和弱映射的内容,那么您还需要知道什么? - Felix Kling
1
我已经阅读了这些内容,但希望能够得到一个示例来限制和扩展类的范围。 - Alamelu Venkat
4
ES6类故意没有私有/受保护的属性与方法:请看这里。WeakMap/Symbols更像是一种解决方案,用于在ES6类中添加私有/受保护的属性与方法,但ES6类更多地是一种定义方法的方式,而不是复制像Java和C#等语言中“经典”面向对象类的方式。 - Adam Botley
3个回答

17

私有属性

在ES6(以及之前),所有私有属性的实现都依赖于闭包

在JavaScript有版本之前,人们就一直在这样做。WeakMap只是一种变体,它消除了为每个新对象创建新作用域和新函数的需要,但代价是访问速度较慢。

Symbol是ES6的一种变体,它将属性隐藏在常见操作中,例如简单的属性访问或for in循环。

var MyClass;
( () => {
  // Define a scoped symbol for private property A.
  const PropA = Symbol( 'A' );
  // Define the class once we have all symbols
  MyClass = class {
    someFunction () {
      return "I can read " + this[ PropA ]; // Access private property
    }
  }
  MyClass.prototype[ PropA ] = 'Private property or method';
})();

// function in the closure can access the private property.
var myObject = new MyClass();
alert( myObject.someFunction() );

// But we cannot "recreate" the Symbol externally.
alert( myObject[ Symbol( 'A' ) ] ); // undefined

// However if someone *really* must access it...
var symbols = Object.getOwnPropertySymbols( myObject.__proto__ );
alert( myObject[ symbols[ 0 ] ] );

如上所示,可以通过 Object.getOwnPropertySymbols() 来解决这个问题。尽管 WeakMap 存在,但我总是选择 symbol。代码更清晰、简单、减少垃圾回收工作,并且(我认为)更有效率。

我个人也避免使用 classObject.create 更加简单。但这超出了范围。


受保护的属性

受保护的属性需要执行函数知道调用代码的对象,以判断是否应该授予访问权限。

在JS中这是不可能的,不是因为ES6没有真正的类,而是因为调用者的上下文根本无法获得

由于JavaScript具有各种 特殊 性质,可预见的未来内受保护的属性仍将是不可能实现的。

[ 更新 ] 三年后,由于模块的广泛支持,现在可以模拟大多数受保护属性的好处,请参见下面 Twifty 的答案。 它们仍然是公共的,但您需要额外的步骤才能访问它们,这意味着很难意外访问或覆盖它们。 [ /更新 ]

或者...


包属性

有些语言有半保护的属性,有时称为“包私有”,其中方法/属性可以被同一模块/包中的成员访问。

ES6可以使用闭包实现它。 它与上面的私有属性代码完全相同 - 只需将范围及其符号与多个原型共享即可。

但这是不切实际的,因为这要求整个模块在同一个封闭范围内定义,即在单个文件中。 但它仍然是一个选项。


7
我很晚才回答,但是在 JavaScript 中模拟私有和受保护的方法是可行的。
私有方法/属性
使用众所周知的符号(Symbol) 方法。
const someMethod = Symbol()
const someProperty = Symbol()

export default class Parent {
  constructor () {
    this[someProperty] = 'and a private property'
  }

  [someMethod] () {
    console.log('this is a private method')
    console.log(this[someProperty])
  }

  callPrivateMethod () {
    this[someMethod]()
  }
}

受保护的方法/属性

由于它们的性质,受保护成员对派生类可见。它们还必须模仿super.method模式。

symbols.js

export default {
   protectedMethod: Symbol()
}

parent.js

import symbols from './symbols'

const someMethod = Symbol()
const someProperty = Symbol()

export default class Parent {
  constructor () {
    this[someProperty] = 'and a private property'
  }

  [someMethod] () {
    console.log('this is a private method')
    console.log(this[someProperty])
  }

  [symbols.protectedMethod] () {
    console.log('I am the parent')
  }

  callPrivateMethod () {
    this[someMethod]()
  }
}

child.js

import Parent from './parent'
import symbols from './symbols'

export default class Child {
  [symbols.protectedMethod] () {
    console.log('I am the child')
    super[symbols.protectedMethod]()
  }

  callProtectedMethod () {
    this[symbols.protectedMethod]()
  }
}

3
这并不是受保护的,而是公开的,因为每个人都可以访问“符号”,或者我漏掉了什么吗? - Benjamin Van Ryseghem
1
@BenjaminVanRyseghem 要访问字段,您需要使用在parent.js中创建的原始Symbol,不能只创建另一个具有相同名称的symbol并期望它也能提供访问。这是模拟保护,就像每种语言都有一种绕过方式一样。在这里使用符号的目的是防止在对象上意外使用相同的字段名称。 - Twifty
要访问这些字段,您需要使用在parent.js中创建的原始符号。您还可以使用import symbols from './symbols' - Benjamin Van Ryseghem
2
@BenjaminVanRyseghem,正是我的观点。如果您的代码意图写入受保护/私有字段,则必须导入符号。假设您发布了一个名为“string”的模块,并打算使用名为'_sum'的私有字段,则任何用户都可以扩展或添加到模块中具有冲突的命名为'_sum'的字段。他们必须查看源代码以查找错误。使用符号可以防止这种情况发生。私有/受保护成员受到意外写入的保护。这不是要将字段隐藏在用户之外,它只是一种安全措施。没有语言可以隐藏这些成员。 - Twifty
1
somePropertysomeMethod存在于全局范围内,使用它们,我可以通过这种方式 parentObj[someProperty] 访问私有属性(根据您所说),因此它们实际上不是私有的。 - Muhammad Awais
通过使用Object.getOwnPropertySymbols()来检索符号,您可以在不导入任何内容的情况下写入"protected/private"字段。另外,我认为您忘记添加class Child extends Parent了。 - soffyo

0

还可以使用符号作为“键”,通过门卫和条件行为启用具有受保护继承的类的版本。

例如,在这个BST中,root是私有的,add/insert返回true。不想要任何直接节点访问。

然而,AVL子类可以同时使用两者以及一些实用函数。

该键允许以独立或基础的方式表达类的观点。甚至可以使用装饰器或受保护的访问映射(而不是通过门卫getter)来更DRY。

class BinarySearchTree {
  #root;

  constructor(sym) {
    this.#root = null;
    this.#access = sym;
  }

  #protectionCheck = () =>
    this.#access === PROTECTED ||
    (() => {
      throw new Error("Ah, ah, ah. You didn't say the magic word.");
    })();

  __root() {
    this.#protectionCheck();
    return this.#root;
  }

  add(val) {
    ...
    return this.#access === PROTECTED ? node : true;
  }
}

class AVL extends BinarySearchTree {
  constructor() {
    super(PROTECTED);
  }

  get #root() {
    return super.__root();
  }

  add = (value) => {
    const node = super.add(value);
    this.#balanceUpstream(node);
    return true;
  };
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接