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

Posted

技术标签:

【中文标题】typescript 访问修饰符和 javascript 访问修饰符有啥区别?在使用打字稿时我应该更喜欢哪一个?【英文标题】:What are the differences between typescript access modifiers and javascript ones? And which one should I prefer while using typescript?typescript 访问修饰符和 javascript 访问修饰符有什么区别?在使用打字稿时我应该更喜欢哪一个? 【发布时间】:2021-09-18 02:14:20 【问题描述】:

Typescript 提供了 public,protectedprivate 关键字来定义成员的可见性或在它们旁边声明的方法,但是,我知道由于 ES6 javascript 允许使用前缀“#”到类成员或方法以达到相同的结果。

为了更好地了解幕后的工作原理,我在 Typescript 中编写了一个玩具类,只是为了看看它是如何在 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 修饰符声明的成员现在都是公共的,尽管理论上任何访问在编译为 javascript 时应该捕获私有成员,我不确定这是否是代码安全的最佳选择。

对于修改成员可见性的最佳方法,您有什么建议吗?

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

【问题讨论】:

看起来 TS 编译器没有使用 JS private 功能。无论哪种方式,使用你喜欢使用的东西。如果您在 TS 中编程,这并没有什么不同,因为编译器会处理可见性/访问问题。 "因为 ES6 Javascript 允许对类成员或方法使用前缀“#”以达到相同的结果。" 1. 不是 ES6,这个是一个最近的变化,还没有正式发布。 2. 仅供private访问,没有“受保护”访问级别。 【参考方案1】:

JavaScript 私有字段语法#

支持

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

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

访问级别

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

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

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访问级别,以允许从子类访问字段:


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 访问修饰符可以在子类中更改以减少限制:

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 中的私有字段语法不会发生这种情况。同样,私有字段对外部完全隐藏。

使用哪个

这取决于选择。如果编写专注于 OOP 的 TypeScript 代码,您可能只想坚持使用 privateprotectedpublic 关键字。他们更好地处理类层次结构。

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

您也可以混合使用这两种封装。

归根结底,这将是每个案例的基础。

【讨论】:

描述得很好。 谢谢!你涵盖的非常好,而且你在每个例子中都非常清楚!【参考方案2】:

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

【讨论】:

根据MDN page,看起来只有在三星互联网浏览器上用于移动领域的问题以及用于移动和桌面方法的Safari时才会出现问题,其余浏览器似乎可以很好地处理它好吧,那么当然对于受保护的人来说,唯一的选择就是打字稿方式。 是的,但有时您还需要查看行业标准。到目前为止,我还没有看到很多开源项目使用它。 @MicheleNuzzi current version of Firefox is 89 和 doesn't support private fields。即将推出的 90 版最终将支持它们。所以,现在它甚至不适用于普通的 FF 用户。 FF ESR 用户可能暂时无法使用它。我不会说这处理得“相当好”

以上是关于typescript 访问修饰符和 javascript 访问修饰符有啥区别?在使用打字稿时我应该更喜欢哪一个?的主要内容,如果未能解决你的问题,请参考以下文章

Swift 3:公共访问修饰符和内部访问修饰符之间的区别? [复制]

访问修饰符和继承

JAVA中的访问修饰符和包

修饰符和多态

Java基础知识回顾之二 ----- 修饰符和String

Java基础知识回顾之二 ----- 修饰符和String