一直以来,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 拜访修饰符只能经过类型系统才干被强制执行。它在运转时没有效果,能够被一个类型断言轻松绕过。不要以为它还能保持数据的躲藏性。
  • 为了更牢靠的信息躲藏,请运用闭包。