typescript 访问修饰符和 javascript 之间有什么区别?在使用打字稿时我应该更喜欢哪一个?

What are the differences between typescript access modifiers and javascript ones? And which one should I prefer while using typescript?

Typescript 提供了 publicprotectedprivate 关键字来定义成员的可见性或在它们旁边声明的方法,但是,我知道自从 ES6 Javascript 允许对 class 成员或方法使用前缀“#”以获得相同的结果。

为了更好地了解幕后工作原理,我用 Typescript 编写了一个玩具-class,只是为了看看它是如何在 javascript 中编译的:

class aClass
{
    #jsPrivate: number;
    get jsPrivate()
        { return this.#jsPrivate};

    private tsPrivate: number;
    protected tsProtected: number;
    public tsPublic: number;

    constructor( a: number, b: number, c: number, d: number)
    {
        this.#jsPrivate = a;
        this.tsPrivate = b;
        this.tsProtected = c;
        this.tsPublic = d;
    }
}

console.log(new aClass(1,2,3,4));

使用 tsc --target es6 和 Typescript 版本 4.3.5 编译的结果变成:

var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _aClass_jsPrivate;
class aClass {
    constructor(a, b, c, d) {
        _aClass_jsPrivate.set(this, void 0);
        __classPrivateFieldSet(this, _aClass_jsPrivate, a, "f");
        this.tsPrivate = b;
        this.tsProtected = c;
        this.tsPublic = d;
    }
    get jsPrivate() { return __classPrivateFieldGet(this, _aClass_jsPrivate, "f"); }
    ;
}
_aClass_jsPrivate = new WeakMap();
console.log(new aClass(1, 2, 3, 4));

我不确定我是否做对了,但我注意到js风格的私有成员现在在全局范围内,而且使用typescript修饰符声明的成员现在都是public,虽然理论上,在编译为 javascript 时,任何访问私有成员的尝试都应该被捕获,我不确定这是否是代码安全的最佳选择。

对于哪一种是修改成员可见性的最佳方式,您有什么建议吗?

您能否也向我解释一下为什么会有这些差异?

前缀“#”的使用相当新,尚未正式可用。如果您使用它,浏览器兼容性也可能是一个问题。所以我建议你现在使用 typescript 的类型,因为它很稳定并且会自行处理跨浏览器支持。 另外,无论如何,你现在在 JS 中没有受保护的访问修饰符。

JavaScript 私有字段语法 #

支持

还不是官方的。 the latest upcoming draft of the specifications. However, the ES2021 specification (ES12) 中包含的语法没有它。所以现在它正处于正式化的过渡状态。

同时,并非所有浏览器都支持私有字段。最值得注意的是 Firefox 89 版(在撰写本文时,浏览器的当前版本)不支持它。即将发布的 90 版将增加对私有字段的支持,但它处于 Beta 阶段。

访问级别

私有字段语法只允许隐藏一个字段。 JavaScript 中没有关于 protected 访问的概念(仅对 class 的后代可见)。因此对于 class 之外的任何代码,字段要么对任何人可见,要么不可见。两者之间没有任何区别。

此外,JavaScript 中的私有字段完全 隐藏。没有官方机制可以从外部以编程方式提取它们并与之交互。只有声明它的 class 才能使用它们。

class Foo {
  #id;
  constructor(num)   { this.#id = num; }
  viewPrivate(other) { return other.#id; }
}

class Bar {
  #id;
  constructor(num) { this.#id = num; }
}

const foo1 = new Foo(1);
const foo2 = new Foo(2);

console.log(foo1.viewPrivate(foo1)); //1
console.log(foo1.viewPrivate(foo2)); //2

const bar = new Bar(3);
console.log(foo1.viewPrivate(bar)); //Error 
                                    // cannot access #x from different class

TypeScript 访问修饰符

支持

TypeScript 访问修饰符在技术上无处不在。那是因为 TypeScript 代码被转换为纯 JavaScript。编译器可以配置目标 ECMAScript 版本。

与类型系统的其他部分一样,访问修饰符将在编译时去除。如果您尝试访问一个您不应该访问的字段,您将遇到编译错误。

访问级别

最大的区别是支持 protected 访问级别以允许从 subclasses:

访问字段

class Foo {
    public    a = 1;
    protected b = 2;
    private   c = 3;
}

class Bar extends Foo {
    doStuff(): number {
        return this.a + //OK - it's public
               this.b + //OK - it's protected and Bar extends Foo
               this.c;  //Error - it's private
    }
}

Playground Link


与普通 JavaScript 相比的另一个大区别是 TypeScript 访问修饰符可以 更改 在 subclasses 中以减少限制:

class A {
    protected x = "hello";
}

class B extends A {
    public x = "world";
}

console.log(new A().x); // Compilation error
console.log(new B().x); // OK

Playground Link


最后,我必须加倍强调 JavaScript 的私有字段与 TypeScript 的 private 访问修饰符有何不同。 TypeScript 不会“隐藏”字段。它会阻止您引用它们。这些字段仍然存在并且可以通过代码正常访问。连编译错误都可以避免:

class Foo {
    private secret = "top secret";
}

const foo = new Foo();

console.log(JSON.stringify(foo)); //"{"secret":"top secret"}" 
console.log((foo as any).secret); //"top secret" 

Playground Link

JavaScript 中的私有字段语法不会发生这种情况。同样,私有字段对外部完全隐藏。

使用哪个

这取决于选择。如果编写面向对象的 TypeScript 代码,您可能只想坚持使用 privateprotectedpublic 关键字。他们在 class 层次结构中表现得更好。

话虽如此,JavaScript 中的私有字段语法 # 如果您想要更强大的不会泄漏的封装,那将非常强大。

您也可以混合使用两种类型的封装。

归根结底,每个人都将视具体情况而定。