我正在研究 TypeScript 中私有成员的实现,我发现它有点令人困惑。Intellisense 不允许访问私有成员,但在纯 JavaScript 中,它就在那里。这让我觉得 TS 没有正确实现私有成员。有什么想法吗?
class Test{
private member: any = "private member";
}
alert(new Test().member);
我正在研究 TypeScript 中私有成员的实现,我发现它有点令人困惑。Intellisense 不允许访问私有成员,但在纯 JavaScript 中,它就在那里。这让我觉得 TS 没有正确实现私有成员。有什么想法吗?
class Test{
private member: any = "private member";
}
alert(new Test().member);
就像类型检查一样,成员的隐私只在编译器内强制执行。
私有属性被实现为常规属性,并且不允许类外的代码访问它。
要在类中使某些东西真正私有,它不能是类的成员,它将是在创建对象的代码内的函数范围内创建的局部变量。这意味着您不能像类成员一样访问它,即使用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++;
}
}
}
编辑
这种方法只能在绝对需要的情况下谨慎使用。例如,如果您需要临时缓存密码。
使用这种模式会产生性能成本(与 Javascript 或 Typescript 无关),并且只能在绝对必要的情况下使用。
由于 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/
一旦对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)();
}
}
}
感谢 Sean Feldman 提供有关此问题的官方讨论的链接 - 请参阅他对链接的回答。
我阅读了他链接到的讨论,这里是关键点的摘要:
this
如果没有某种解决方法,它们就无法访问@private
使用能够识别注释的缩小器对
私有方法进行注释,从而可以有效地缩小方法名称在发出的代码中添加可见性支持的总体反对论点:
我意识到这是一个较早的讨论,但分享我对 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 中查看该实例时,您不会看到它的所有“私有”成员与真正的公共成员混合在一起(在正确重构的真实代码中可能会在视觉上变得非常混乱),而是看到它们整齐地分组在折叠__
属性中:
这是添加适当私有属性的可重用方法:
/**
* 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
。
值得注意的是,在大多数情况下, classPrivate
只是提供了一个可读性很好的签名,而直接使用 ofWeakMap
却没有。
其实这个问题的答案很简单。
你有这个代码:
class Test{
private member: any = "private member";
}
alert(new Test().member);
在该代码中,您混合了两种不同的语言。这部分是 TypeScript:
class Test{
private member: any = "private member";
}
这部分是JavaScript:
alert(new Test().member);
字段类中的private
关键字用于 TypeScript。因此 TypeScript 中的其他类无法访问该字段,因为 TypeScript 编译器不允许它。例如,如果您尝试这样做,它将无法正常工作:Test
member
member
class Test2 {
constructor() {
var test = new Test();
test.member = "Cannot do this";
}
}
是否放置private
或public
放在生成的 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 做什么。
在某些情况下,您使用 TypeScript 编写代码,但公众可能会使用生成的 JavaScript,也许是一个插件,并且您不希望他们破坏事物,那么在这种情况下您会担心它。对于这些情况,您将编写 TypeScript 代码以使字段private
甚至在 JavaScript 端。其他答案已经涵盖了如何做到这一点。