为什么我可以访问 TypeScript 私有成员时,我不应该能够?

我正在研究 TypeScript 中私有成员的实现,我发现它有点令人困惑。智能感知不允许访问私有成员,但是在纯 JavaScript 中,这些都存在。这使我认为 TS 没有正确地实现私有成员。 有什么想法吗?

class Test{
private member: any = "private member";
}
alert(new Test().member);
111215 次浏览

正如类型检查一样,成员的隐私只在编译器中强制执行。

私有属性作为常规属性实现,类外的代码不允许访问它。

要在类中创建真正私有的东西,它不能是类的成员,它应该是在创建对象的代码中的函数作用域内创建的局部变量。这意味着您不能像类的成员一样访问它,即使用 this关键字。

JavaScript 确实支持私有变量。

function MyClass() {
var myPrivateVar = 3;


this.doSomething = function() {
return myPrivateVar++;
}
}

在 TypeScript 中,这将被表达如下:

class MyClass {


doSomething: () => number;


constructor() {
var myPrivateVar = 3;


this.doSomething = function () {
return myPrivateVar++;
}
}
}

剪辑

这种方法应该只在绝对需要的地方使用 有节制。例如,如果需要临时缓存密码。

There are performance costs to using this pattern (irrelevant of Javascript or Typescript) and should only be used where absolutely necessary.

一旦对 弱点地图的支持得到更广泛的应用,就会出现一种有趣的技术,详见示例 # 3 给你

It allows for private data AND avoids the performance costs of Jason Evans example by allowing the data to be accessible from prototype methods instead of only instance methods.

链接的 MDN WeakMap 页面列出了 Chrome 36、 Firefox 6.0、 IE 11、 Opera 23和 Safari 7.1的浏览器支持。

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
decrement() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}

In TypeScript Private functions are only accessible inside the class. Like

enter image description here

当你尝试访问一个私有成员时,它会显示一个错误,下面是一个例子:

enter image description here

注意: 使用 javascript 就可以了,两个函数都可以访问 outside.

感谢肖恩费尔德曼为这个问题的官方讨论的链接-见 他的回答的链接。

我读了他链接的讨论,还有 以下是关键要点的摘要:

  • Suggestion: private properties in constructor
    • 问题: 无法从原型函数访问
  • 建议: 构造函数中的私有方法
    • 问题: 与属性相同,另外,在原型中每个类创建一个函数会失去性能优势; 相反,您需要为每个实例创建一个函数副本
  • Suggestion: add boilerplate to abstract property access and enforce visibility
    • 问题: 主要性能开销; TypeScript 是为大型应用程序设计的
  • 建议: TypeScript 已经将构造函数和原型方法定义封装在一个闭包中; 将私有方法和属性放在那里
    • 在闭包中放置私有属性的问题: 它们变成静态变量; 每个实例没有一个静态变量
    • 在闭包中放置私有方法的问题: 如果没有某种变通方法,他们就无法访问 this
  • 建议: 自动删除私有变量名
    • counter arguments: that's a naming convention, not a language construct. Mangle it yourself
  • 建议: 使用 @private对私有方法进行注释,以便能够识别出注释可以有效地缩小方法名称的缩小器
    • 没有明显的反对意见

在发出的代码中添加可见性支持的总体反对意见:

  • 问题在于 JavaScript 本身没有可见性修饰符——这不是 TypeScript 的问题
  • 在 JavaScript 社区中已经有了一个固定的模式: 在私有属性和方法前加一个下划线,表示“自行承担风险”
  • 当 TypeScript 设计者说真正的私有属性和方法是“不可能的”时,他们的意思是“在我们的设计约束下是不可能的”,特别是:
    • 发出的 JS 是惯用的
    • 样板文件很少
    • 与普通的 JS OOP 相比,没有额外的开销

我意识到这是一个老的讨论,但它可能仍然是有用的,共享我的解决方案的问题,所谓的私有变量和方法在 TypeScript“泄漏”到编译后的 JavaScript 类的公共接口。

To me this issue is purely cosmetic, i.e. it's all about the visual clutter when an instance variable is viewed in DevTools. My fix is to group private declarations together inside another class that is then instantiated in the main class and assigned to a private (but still publicly visible in JS) variable with a name like __ (double underscore).

Example:

class Privates {
readonly DEFAULT_MULTIPLIER = 2;
foo: number;
bar: number;


someMethod = (multiplier: number = this.DEFAULT_MULTIPLIER) => {
return multiplier * (this.foo + this.bar);
}


private _class: MyClass;


constructor(_class: MyClass) {
this._class = _class;
}
}


export class MyClass {
private __: Privates = new Privates(this);


constructor(foo: number, bar: number, baz: number) {
// assign private property values...
this.__.foo = foo;
this.__.bar = bar;


// assign public property values...
this.baz = baz;
}


baz: number;


print = () => {
console.log(`foo=${this.__.foo}, bar=${this.__.bar}`);
console.log(`someMethod returns ${this.__.someMethod()}`);
}
}


let myClass = new MyClass(1, 2, 3);

当在 DevTools 中查看 myClass实例时,你不会看到它的所有“私有”成员与真正的公共成员混杂在一起(在正确重构的现实生活代码中,这些成员在视觉上会变得非常凌乱) ,而是看到它们整齐地分组在折叠的 __属性中:

enter image description here

由于 TypeScript 3.8将被发布,您将能够声明私有字段的 无法访问,甚至无法在包含类之外检测到

class Person {
#name: string


constructor(name: string) {
this.#name = name;
}


greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}


let jeremy = new Person("Jeremy Bearimy");


jeremy.#name
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

私有字段以 #字符开头

Please note that these private fields will be something different than fields marked with private keyword

Ref. https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/

以下是添加适当私有属性的可重用方法:

/**
* Implements proper private properties.
*/
export class Private<K extends object, V> {


private propMap = new WeakMap<K, V>();


get(obj: K): V {
return this.propMap.get(obj)!;
}


set(obj: K, val: V) {
this.propMap.set(obj, val);
}
}

假设您的 Client类需要两个私有属性:

  • prop1: string
  • prop2: number

下面是你如何实现它:

// our private properties:
interface ClientPrivate {
prop1: string;
prop2: number;
}


// private properties for all Client instances:
const pp = new Private<Client, ClientPrivate>();


class Client {
constructor() {
pp.set(this, {
prop1: 'hello',
prop2: 123
});
}


someMethod() {
const privateProps = pp.get(this);


const prop1 = privateProps.prop1;
const prop2 = privateProps.prop2;
}
}

如果您所需要的只是一个私有属性,那么它将变得更加简单,因为在这种情况下您不需要定义任何 ClientPrivate

Worth noting, that for the most part, class Private just offers a nicely readable signature, whereas direct use of WeakMap does not.

其实这个问题的答案很简单。

你有这个密码:

class Test{
private member: any = "private member";
}
alert(new Test().member);

在这段代码中,你混合了两种不同的语言,这部分是 TypeScript:

class Test{
private member: any = "private member";
}

这部分是 JavaScript:

alert(new Test().member);

member字段的 Test类中的 private关键字用于 TypeScript。因此 TypeScript 中的其他类无法访问 member字段,因为 TypeScript 编译器不允许这样做。例如,如果你尝试这样做,它不会起作用:

class Test2 {
constructor() {
var test = new Test();
test.member = "Cannot do this";
}
}

privatepublic放在生成的 JavaScript 上并没有什么区别。生成的 JavaScript 代码总是:

var Test = (function () {
function Test() {
this.member = "private member";
}
return Test1;
}());

因此,您可以这样做,因为 JavaScript 允许这样做:

alert(new Test().member);

这不是一条刻在石头上的规则,也许有些情况我不知道,但如果你使用 TypeScript,那么为什么要担心你可以使用 JavaScript 做什么; 这个想法是,你不再写 JavaScript,所以你可以/不能在 JavaScript 中做什么不应该再担心了。对我来说,这种担心就像编写 C # 代码,然后说为什么我能够用 CIL或汇编语言改变私有字段一样。我不确定 CIL 是否允许,但这不是重点。关键在于,在用 C # 编写代码之后,不要到处查看 CIL 可以做什么。

There may be cases where you write code in TypeScript but the public may use the generated JavaScript, a plugin perhaps, and you don't want them to break things, well in that case you would worry about it. For those cases, you would write your TypeScript code to make the fields private even on the JavaScript side. Other answers have already covered on how to do that.

摘要 -类型系统将抛出警告消息。但是 private 是一个特定于类型系统的特性,所以它在运行时会消失。

在这里阅读我写的一篇关于访问 TypeScript 私有变量的文章: https://szaranger.medium.com/stop-relying-on-private-to-hide-variables-in-typescript-3c45d25a58d0