为什么我不能访问TypeScript私有成员?


108

我正在研究TypeScript中私有成员的实现,但我感到有些困惑。Intellisense不允许访问私有成员,但是在纯JavaScript中,仅此而已。这使我认为TS无法正确实现私有成员。有什么想法吗?

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

您想知道为什么IntelliSense不会在具有alert()的行上给您私人成员吗?
安排

7
不。我想知道为什么TS具有私有功能,而这只能用于智能感知,而不能真正用于其编译的JavaScript。在typescriptlang.org/Playground中执行的此代码会提醒私有成员值。
肖恩·费尔德曼

如前所述,必须将项目声明为私有上下文中的变量才能使其私有。我猜打字稿不会这样做,因为与添加到原型相比,打字稿效率低下。它也弄乱了类型定义(私有成员实际上不是该类的一部分)
Shane

如果要在原型上存在真正的私有变量,这确实会花费一些开销,但是我已经编写了一个名为ClassJS的库,该库仅在GitHub上做过github.com/KthProg/ClassJS
KthProg

Answers:


97

就像类型检查一样,成员的隐私仅在编译器中强制执行。

私有属性被实现为常规属性,并且不允许类外的代码对其进行访问。

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


25
对于Javascript程序员来说,将局部变量放入对象构造函数并将其用作私有字段并不罕见。我很惊讶他们不支持这样的事情。
埃里克(Eric)

2
@Eric:由于TypeScript使用方法的原型而不是将方法作为原型添加到构造函数中,因此无法从方法访问构造函数中的局部变量。可以在该类的函数包装内创建局部变量,但是我还没有找到一种方法来做到这一点。但是,那仍然是局部变量,而不是私有成员。
2012年

40
我一直在提供反馈。我相信它应该提供创建显示模块模式的选项,这样私有成员可以保持私有状态,而公共成员可以在JavaScript中访问。这是一种常见的模式,在TS和JS中将提供相同的可访问性。
约翰·帕帕

您可以使用私有静态成员的解决方案:basarat.com/2013/03/real-private-static-class-members-in.html
basarat 2013年

1
@BasaratAli:这是一个静态变量,可在类的方法内使用,但它不是类的成员,即您不能使用this关键字来访问它。
Guffa

37

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

编辑

仅应在绝对需要时谨慎使用此方法。例如,如果您需要临时缓存密码。

使用此模式会降低性能(与Javascript或Typescript无关),仅应在绝对必要的情况下使用。


打字稿不是总是通过设置var _this在作用域函数中使用来做到这一点吗?为什么在类范围内会有疑虑呢?
DrSammyD

否。var _this只是对此的引用。
马丁

2
更准确地称它们为构造函数变量,而不是私有变量。这些在原型方法中不可见。
罗曼·科斯

1
哦,是的,很抱歉,问题是另一个问题,因为对于您创建的每个实例,都将再次创建doSomething,因为它不属于原型链。
Barbu Barbu

1
@BarbuBarbu是的,我同意。这是使用此方法的一个大问题,也是应避免使用它的原因之一。
马丁

11

一旦对WeakMap的支持得到了广泛的应用,这里的示例3中就会详细介绍一种有趣的技术。

它允许私有数据,并通过允许从原型方法而不是实例方法访问数据来避免Jason Evans示例的性能成本。

链接的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)();
    }
  }
}

我喜欢!基本上,这意味着将私有属性隐藏到聚合类中。最有趣的将是...添加protected参数支持如何?:D
罗曼·科斯

2
@RamtinSoltani链接的文章统计,由于弱映射的工作方式,这不会阻止垃圾收集。如果有人希望在使用此技术时更加安全,那么他们可以实现自己的处置代码,该代码从每个弱映射中删除类实例键。
瑞安·托马斯

1
在MDN页面上:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…。相比之下,本机WeakMap保留对关键对象的“弱”引用,这意味着在没有其他对关键对象的引用的情况下,它们不会阻止垃圾回收。这也避免了防止在映射中垃圾回收值。
瑞安·托马斯

@RyanThomas是的,那是我前一段时间留下的老评论。与Maps相比,WeakMaps不会导致内存泄漏。因此使用此技术是安全的。
Ramtin Soltani

@RamtinSoltani所以要删除您的旧评论吗?>
ErikE

10

由于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.

私有字段以#字符开头

请注意,这些私有字段与标有private关键字的字段有所不同

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


4

感谢Sean Feldman提供了有关此问题的正式讨论的链接- 链接请参阅他的答案

我阅读了他链接的讨论,这是关键点的摘要:

  • 建议:构造函数中的私有属性
    • 问题:无法从原型功能访问
  • 建议:构造函数中的私有方法
    • 问题:与属性相同,而且您失去了在原型中为每个类创建一次函数的性能优势;而是为每个实例创建函数的副本
  • 建议:添加样板以抽象属性访问并增强可见性
    • 问题:主要的性能开销;TypeScript专为大型应用程序而设计
  • 建议: TypeScript已经将构造函数和原型方法定义包装在一个闭包中。将私有方法和属性放在那里
    • 将私有属性放在该闭包中的问题:它们成为静态变量;每个实例没有一个
    • 将私有方法放入该闭包中的问题:如果没有this某种解决方法,它们将无法访问
  • 建议:自动修改私有变量名称
    • 反参数:这是命名约定,不是语言构造。自己弄乱
  • 建议:使用@private使注释器识别私有方法可以有效地缩小方法名称的注释器来 注释私有方法
    • 对此没有重大反驳

在发出的代码中增加可见性支持的总体反论点:

  • 问题是JavaScript本身没有可见性修饰符-这不是TypeScript的问题
  • JavaScript社区中已经建立了一个模式:在私有属性和方法前加下划线,表示“后果自负”。
  • 当TypeScript设计师说真正的私有属性和方法不是“可能的”时,他们的意思是“在我们的设计约束下是不可能的”,特别是:
    • 发出的JS是惯用的
    • 样板最小
    • 与普通JS OOP相比,没有额外的开销

如果这个答案来自这次谈话:typescript.codeplex.com/discussions/397651-,请提供链接:D
Roman M. Koss

1
是的,那是对话-但我链接了Sean Feldman对这个问题的回答,他在其中提供了链接。由于他完成了查找链接的工作,因此我想对他表示赞赏。
alexanderbird

2

在TypeScript中,私有函数只能在类内部访问。喜欢

在此处输入图片说明

当您尝试访问私有成员时,它将显示错误。这是示例:

在此处输入图片说明

注意:使用javascript很好,并且都可以从外部访问这两个函数。


4
OP:“但是在纯JavaScript中,就到了”-我认为您不能解决生成的JavaScript公开公开“私有”功能的问题
Alexanderbird

1
@alexanderbird我想他想说TypeScript通常就足够了。当我们使用TypeScript开发时,我们将其留在项目范围内,因此JavaScript的私密性并不重要。因为首先,原始代码对开发人员而言很重要,而不是对转码(JavaScript)而言很重要。
罗曼·科斯

1
除非你的写作和出版的JavaScript库,那么transpiled代码做事情
alexanderbird

您的答案是题外话。
canbax

1

我意识到这是一个较旧的讨论,但是在TypeScript中“泄漏”到已编译的JavaScript类的公共接口中时,解决我的私有变量和方法的问题可能仍然有用。

对我来说,这个问题纯粹是表面上的问题,即与在DevTools中查看实例变量时的视觉混乱有关。我的解决方法是将私有声明分组到另一个类中,然后在主类中实例化该类,然后将其分配给一个private(但仍在JS中公开可见)名称为__(双下划线)的变量。

例:

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

myClass在DevTools中查看该实例时,您没有看到它们的所有“私有”成员与真正的公共成员混合在一起(在正确重构的现实生活代码中,它们在视觉上会非常混乱),您会看到它们整齐地分组在折叠后的__属性中:

在此处输入图片说明


1
我喜欢。看起来很干净。

0

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

/**
 * 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在这种情况下您无需定义任何私有属性。

值得注意的是,在大多数情况下,类Private仅提供一个可读性很好的签名,而直接使用WeakMap则不能。

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.