一直以来,JavaScript都缺少一种使类的特点成为私有的办法。一般的变通办法是将下划线作为不属于公共API的字段的前缀:
class Foo {
_private = "secret123";
}
但这仅仅主张用户不要拜访私人数据,而它很容易被绕开:
const f = new Foo();
f._private; // "secret123"
TypeScript增加的public、protected和private拜访修饰符,好像供给了一些强制执行的功能:
class Diary {
private secret = "cheated on my English test";
}
const diary = new Diary();
diary.secret
// ---- 特点 "secret"为私有特点,只能在类 "Diary"中拜访
可是private是类型系统的一个特性,并且和类型系统中的所有特性相同,它在运转时就不见了,下面是这段代码在Typescript中编译成JavaScript时的样子(用target=ES2017):
class Diary {
constructor () {
this.secret = "cheated on my English test";
}
}
const diary = new Diary();
diary.secret;
private修饰符消失了,你的隐秘(secret)也就暴露了!就像_private常规相同,Typescript的拜访修饰符只会阻止你拜访私有数据。经过类型断言,你甚至能够从Typescript内部拜访一个私有特点:
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
const p = new Person('Alice');
console.log(p.name); // error: Property 'name' is private and only accessible within class 'Person'.
(p as any).name = 'Bob'; // no error, but this is not type safe and should be avoided
p.greet(); // output: Hello, my name is Bob
在上面的代码中,尽管运用了private修饰符将name成员设为私有的,可是在类的外部,仍然能够经过类型断言将p的类型转换为any类型,然后拜访和修改name成员。这显然是不安全的。
因而,尽管private修饰符供给了必定程度上的封装性和安全性,可是仍需求开发者自觉遵守规则,防止不必要的类型断言和其他方式的拜访和修改。
换句话说,不能依赖私有拜访操作符private来躲藏信息!
那么如果真的要来点强有力的办法的话,应该要怎么做?传统的答案是利用JavaScript牢靠的躲藏信息的办法之一–闭包。先看下下面的代码:
declare function hash(text: string): number;
class PasswordChecker {
checkPassword: (password: string) => boolean;
constructor (passwordHash: number) {
this.checkPassword = (password: string) => {
return has(password) === passwordHash;
}
}
}
const checker = new PasswordChecker(hash("s3cret"));
checker.checkPassword("s3cret"); // 返回 true
JavaScript没有供给从PasswordChecker的结构函数之外拜访 passwordHash 变量的办法,可是这的确也有一些缺点:特别是,因为passwordHash在结构函数之外是不行见的,所以每个运用它的办法也有必要在那里定义。这就导致每个类的实例都要创建一个办法的副本,然后导致更高的内存运用量。它还会阻止同一类的其他实例拜访私有数据。闭包或许是不方便的,但它肯定会确保你数据的私有性!
一个新的挑选是运用私有字段,从 TypeScript 3.8 开始,引入了一种新的语法糖 #
来支持私有字段。在类中,经过在变量名前增加 #
符号来表示这是一个私有字段,外部无法直接拜访该字段。以下是一个示例:
class MyClass {
#privateField: string;
constructor(privateField: string) {
this.#privateField = privateField;
}
public publicMethod() {
console.log(this.#privateField);
}
}
const instance = new MyClass("private value");
instance.publicMethod(); // 输出 "private value"
console.log(instance.#privateField); // 语法错误,无法拜访私有字段
运用 #
能够愈加明晰地标明变量是私有的,并且能够确保外部无法直接拜访私有字段。但需求留意的是,这种语法仍然需求编译成 JavaScript 才干运转,并且一些老旧的浏览器或 Node.js 版别或许不支持该语法。
最后,如果你忧虑的是安全问题,而不仅仅是封装问题,那么还有其他需求留意的问题,比方对内置原型(prototype)和函数的修改。
需求记住的工作
- private 拜访修饰符只能经过类型系统才干被强制执行。它在运转时没有效果,能够被一个类型断言轻松绕过。不要以为它还能保持数据的躲藏性。
- 为了更牢靠的信息躲藏,请运用闭包。