Typescript:不能依赖修饰符private来隐藏信息
一直以来,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 访问修饰符只能通过类型系统才能被强制执行。它在运行时没有效果,可以被一个类型断言轻松绕过。不要以为它还能保持数据的隐藏性。
- 为了更可靠的信息隐藏,请使用闭包。
转载自:https://juejin.cn/post/7203690731276386360