Typescript: 支持最终类(不可子类化)

创建于 2016-04-26  ·  124评论  ·  资料来源: microsoft/TypeScript

我认为有一种方法可以指定一个类不应该被子类化,这样编译器会在编译时警告用户,如果它看到另一个扩展原始类的类。

在 Java 上,标有 final 的类无法扩展,因此在 TypeScript 上使用相同的关键字,它看起来像这样:

final class foo {
    constructor() {
    }
}

class bar extends foo { // Error: foo is final and cannot be extended
    constructor() {
        super();
    }
}
Suggestion Won't Fix

最有用的评论

方法还应该有一个final修饰符:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

  }
}
// => Method fooIt of Bar cannot override fooIt of Foo, because it is final.

例如,我经常使用以下模式,我想紧急避免 fooIt 被覆盖:

import Whatever ...

abstract class Foo {
  private ImportantVariable:boolean;

  protected abstract fooIt_inner:Whatever();

  public final fooIt():Whatever() {
    //do somestate change to aprivate member here, which is very crucial for the functionality of every Foo:
    ImportantVariable = true;
    //call the abstract inner functionality:
    return this.fooIt_inner();    
  }
}

所有124条评论

具有私有构造函数的类是不可扩展的。 考虑改用这个。

根据我的回忆,我确信编译器不喜欢构造函数上的 private 关键字。 也许我没有使用粘贴版本

这是一个新功能,将在 TS 2.0 中发布,但您可以使用typescript@next尝试它。 有关更多详细信息,请参阅https://github.com/Microsoft/TypeScript/pull/6885

好的谢谢

私有构造函数不是也使类不能从类中实例化吗? 这不是期末班的正确答案。

Java 和/或 C# 使用final类在运行时优化您的类,知道它不会被专门化。 我认为这是最终支持的主要价值。 在 TypeScript 中,没有任何东西可以让您的代码运行得比没有 final 时更好。
考虑使用注释来通知您的用户正确使用类,和/或不公开您打算成为最终的类,而是公开它们的接口。

我不同意,相反,我同意段要。 Private 不能解决这个问题,因为我还希望最终的类可以使用构造函数实例化。 也不将它们暴露给用户会迫使我为它们编写额外的工厂。 对我来说,最终支持的主要价值在于,它可以防止用户犯错误。
这样争论:当我在函数签名中使用类型时,TypeScript 提供了什么来使我的代码运行得更快? 不也是为了防止用户出错吗? 我可以编写注释来描述用户应该将哪些类型的值作为参数传递。 很遗憾,像final关键字这样的扩展被推掉了,因为在我看来它与typescript的原始内涵相冲突:通过添加编译级别使JavaScript更安全,该级别执行尽可能多的检查以避免尽可能多的错误尽可能提前。 还是我误解了 TypeScript 的意图?

方法还应该有一个final修饰符:

class Foo {
  final fooIt():void{

  }
}

class Bar {
  fooIt():void {

  }
}
// => Method fooIt of Bar cannot override fooIt of Foo, because it is final.

例如,我经常使用以下模式,我想紧急避免 fooIt 被覆盖:

import Whatever ...

abstract class Foo {
  private ImportantVariable:boolean;

  protected abstract fooIt_inner:Whatever();

  public final fooIt():Whatever() {
    //do somestate change to aprivate member here, which is very crucial for the functionality of every Foo:
    ImportantVariable = true;
    //call the abstract inner functionality:
    return this.fooIt_inner();    
  }
}

关于成本与效用的争论是一个相当主观的争论。 主要关注的是每个新功能、构造或关键字都会增加语言和编译器/工具实现的复杂性。 我们在语言设计会议中尝试做的是了解权衡,并且仅在附加值超过引入的复杂性时才添加新功能。

该问题未锁定以允许社区成员继续添加反馈。 有了足够的反馈和引人注目的用例,问题就可以重新打开。

实际上 final 是一个非常简单的概念,不会给语言增加任何复杂性,应该添加它。 至少对于方法。 它增加了价值,当很多人在一个大项目上工作时,不允许有人覆盖不应该被覆盖的方法是有价值的。

在 TypeScript 中,没有任何东西可以让您的代码运行得比没有 final 时更好。

哇塞! 静态类型也不会使您的代码运行得更好,但安全性是一件好事。

最终(密封)就在那里,具有覆盖作为我希望看到的使类自定义更安全的功能。 我不在乎性能。

静态类型也不会使您的代码运行得更好,但安全性是一件好事。

确切地。 正如private阻止其他人调用该方法一样, final限制其他人覆盖该方法。

两者都是该类与外部世界的 OO 接口的一部分。

完全同意@pauldraper和@mindarelus。 请执行此操作,这将很有意义,我目前非常想念它。

我不认为 final 只对性能有益,它也对设计有益,但我认为它在 TypeScript 中根本没有意义。 我认为通过跟踪Object.freezeObject.seal的可变性影响可以更好地解决这个问题。

@aluanhaddad你能更详细地解释一下吗? 为什么你认为它“在 TypeScript 中根本没有意义”?
冻结或密封对象意味着不允许向对象添加新属性,但不会阻止向派生对象添加属性,因此即使我密封了基类,我仍然可以覆盖子类中的方法,它扩展了该基类. 另外,我无法在运行时向基类添加任何属性。

在我看来,在 java 中的类或类方法上使用final的想法更多地与为了线程安全而最小化对象的可变性有关。 (条目 15。Joshua Bloch,Effective Java)

我不知道这些原则是否会延续到 javascript 中,因为 JS 中的一切都是可变的(如果我错了,请纠正我)。 但是 Typescript 不是 Javascript,是吗?

我真的很想看到这个实现。 我认为这将有助于创建更健壮的代码。 现在......如何转化为JS,老实说可能没有必要。 它可以只停留在我们其余的编译时检查所在的围栏的打字稿一侧。

当然我可以没有它,但这是打字稿的一部分,对吧? 仔细检查我们被忽视的错误?

对我来说, final在打字稿中扮演与private或打字相同的角色,即代码契约。 它们可用于确保您的代码合同不会被破坏。 我非常喜欢它。

@hk0i在第 17 条(第 2 版)中也以类似于此处回应的方式提及:

但是普通的具体类呢? 传统上,它们既不是最终的,也不是为子类化而设计和记录的,但这种状态是危险的。 每次在这样的类中进行更改时,扩展该类的客户端类都有可能中断。 这不仅仅是一个理论问题。 在修改非最终具体类的内部结构后,收到与子类化相关的错误报告并不少见,该类不是为继承而设计和记录的。

此问题的最佳解决方案是禁止在未设计和记录为安全子类化的类中进行子类化。 有两种方法可以禁止子类化。 两者中更容易的是将类声明为final。 另一种方法是将所有构造函数设为私有或包私有,并添加公共静态工厂来代替构造函数。

鉴于抽象关键字已经存在,我认为它不会增加语言的认知复杂性。 但是,我不能谈论它的实现/性能影响,并且绝对尊重从这个角度保护语言。 我认为将这些关注点分开对于决定是否实施此功能会很有成效。

我相信final将是密封类的一个很好的补充。 一个用例是您的类中可能有很多公共方法,但通过接口只公开其中的一个子集。 您可以快速对实现进行单元测试,因为它具有所有这些公共方法,而真正的使用者使用接口来限制对它们的访问。 能够密封实现将确保没有人扩展实现或更改公共方法。

您还可以确保没有人继承您的类。 TypeScript 应该在那里强制执行这些规则,关于注释的建议似乎是解决这个用例的一种懒惰的方法。 我读到的另一个答案是关于使用 private ,它只适用于特定情况,但不适用于我上面解释的那个。

像这个线程中的许多人一样,我会投票支持密封类。

@mhegazy私有构造函数和密封/最终类具有非常不同的语义。 当然,我可以通过定义私有构造函数来防止扩展,但是我也不能从类外部调用构造函数,这意味着我需要定义一个静态函数来允许创建实例。

我个人主张进行密封/最终编译器检查,以确保标记为密封/最终的类和方法不能被扩展或覆盖。

在我的问题的上下文中,我希望能够拥有一个公共构造函数,以便我可以实例化对象,但防止类的扩展,并且只有添加sealed/final 才会允许。

有一个任务 - 编写代码

  • 操作不可变对象
  • 继承免费
  • 免费的反射方法

恕我直言,这里需要final关键字。

我认为final是一种很好的方式,可以提醒您自己和代码的用户哪些受保护的方法应该被覆盖,哪些不应该被覆盖。 作为旁注,我也支持明确声明您正在重写,部分是为了让读者知道,但如果您打错了方法的名称,编译器也会抱怨。

@zigszigsdk由于方法永远不会被覆盖,这将如何工作?

对于final
与现在的工作方式相同,除了如果从this上下文中隐藏了 super 的方法(已声明为 final),编译器会抱怨。

对于override
与现在的工作方式相同,除了如果声明了一个方法override并且它的 super 没有该名称的方法,或者它确实有一个方法但它被声明为final ,编译器会抱怨
如果您从this上下文中隐藏 super 的方法并且不声明覆盖,它也可能会警告您。

Java 和/或 C# 使用 final 类在运行时优化您的类,知道它不会被专门化。 我认为这是最终支持的主要价值。

@mhegazy不完全是! 另一个重要的功能是明确类的哪些部分需要被覆盖。

例如,参见“Effective Java”中的第17条:“为继承设计和记录否则禁止它”:

在修改非最终具体类的内部结构后,收到与子类化相关的错误报告并不少见,该类不是为继承而设计和记录的。 此问题的最佳解决方案是禁止在未设计和记录为安全子类化的类中进行子类化。 有两种方法可以禁止子类化。 两者中更容易的是将类声明为final。

在我看来,最终方法也是如此。 一个类被设计为支持覆盖其任何公共方法是非常罕见的。 这将需要非常复杂的设计和大量的测试,因为您必须考虑可能由非覆盖和覆盖行为的组合产生的每种可能的状态组合。 因此,程序员可能会将所有公共方法声明为 final,除了一两个,这将显着减少此类组合的数量。 更常见的是,一两个可覆盖的方法正是所需要的。

我认为final类和方法非常重要。 我希望你能实施它。

另一个引人注目的用户案例@mhegazy 。 今天学习了模板方法模式,发现如维基百科final来禁止子类改变模板方法。 但是我不能在我可爱的 ​​TypeScript 中做到这一点。 太遗憾了!

模板模式允许子类重新定义算法的某些步骤而不改变算法的结构

对我来说,这就像在继承的情况下尝试强制组合一样简单。 没有 final 你不能这样做。

尽管我也很希望看到final出现在打字稿中,但我认为微软试图向我们传达的基本信息是,如果我们想要final ,我们应该使用一种语言支持它。

我希望看到这个问题重新开放和实施。

在构建您将要在内部使用或公开共享(例如在 npm 上)的库时,您不希望使用它的人必须浏览代码并搜索注释或文档以查看该类是否可以/可以不会被覆盖。 如果他们覆盖它会抛出一个错误。

在我的代码中,我有一个被扩展的类,当发生某种事件时,它会触发一个方法。 如果子类定义了该方法,则它将使用该方法,否则它将回退到默认方法。 然而,类中还有其他方法不是“回退”方法,它们更多是辅助方法,例如向 DOM 添加一个项目,在这种情况下,我不希望这些方法被覆盖。

我对这个话题的 5 美分是应该重新开放和实施。

我会用它来强制库的客户创建他们自己的具体类,而不是从其他现有的类中扩展。 就像@lucyvas说的,喜欢组合。 或者只是强制执行新的具体类实现以提供 angular 中的依赖注入。

我也投票支持重新开放,同时支持类和方法

我不是说我们不应该只是不确定 final 关键字的模式在打字稿的上下文中如何工作?

final 关键字不会阻止用户/程序员重写/继承所述类/方法吗?

我看待 Final 的方式是“冻结”数据,这意味着它不能再更改了。 我的意思是这可以很好地使用它,尽管对于想要“错误”修复或改变某些行为的人来说确实很麻烦。

@niokasgami Freeze 与 [data] 相关 (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/freeze)
并且已经作为 ES6 的一部分提供。 Final 适用于类和方法(变量可以是只读的,因此作为“final”,因为我们不能覆盖超级变量)。

“Final”关键字用在JAVA 中,而 Sealed 在C# 中

由于 typescript 受到 C# 的强烈启发,因此“密封”将被选中的可能性应该更高。 现在,我们可能会与ES6 密封混淆,它基本上在运行时密封对象?

@Xample我认为你也可以在 Java 的局部变量上使用 final。 我记得我在编写 Android 应用程序时必须这样做。 我认为它需要我通过一个事件来做,但我不是 100% 确定。

function(){
    let final myVariable = 'Awesome!'
    document.addEventListener('scroll', e => {
        console.log(myVariable)
        myVariable = 'Not Awesome' // Throws an error
    })
}

@TheColorRed好吧,Java 中的最终局部变量意味着常量,不是吗?

@EternalPhane我猜是这样,我一直在类似全局的范围内使用const ......从来没有想过在函数或方法中使用它......我猜我之前的评论是无效的......

@Xample是的,我从 C# 中看到了您对 Sealed 的观点,它的工作方式不一样老实说,我认为这两个关键字 seal 和 final 都无法真正进入该类别,因为两者都可能令人困惑(密封等),而 Final 我们不确定如何应用它,因为在 Java 中它被用作常量:/

在 Java 中,您可以final任何变量、方法或类。 对于变量,这意味着不能重新分配引用(对于非引用类型,这意味着您也不能更改它们的值)。 对于类,这意味着没有扩展( extends ... 关键字)。 对于方法,这意味着在子类中没有覆盖。

java中final主要原因:

  • 减少变量的不变性:为了线程安全
  • 终结类,使它们不能被继承:强制组合而不是继承,因为当您的子类开始无意中调用超级方法时,继承本身就很复杂
  • 防止简单的编程错误:如果您无意更改值,好消息是不再可能
  • 用于大多数人在想象常量时的想法:随处可见的可重用值,如public static final String KEY = "someStringKeyPathHere" ,可用于减少错误,但具有额外的编译时间好处,即不会为相同的值重新创建多个字符串对象

我可能遗漏了几个用例,但我试图保持它非常通用并给出一些例子。

当您在回调(匿名类)之外声明一个变量然后在内部引用它时, @TheColorRed Android 需要它。 我认为这再次与线程安全问题保持一致。 我认为他们试图阻止您从另一个线程重新分配变量。

尽管它很有用,但我认为我们不会很快看到此功能。 现在可能是查看其他解决方案的好时机,或者在我的情况下,将 typescript 放在一起并处理 JS 中的草率。 Javascript 有很多“好处”,因为它没有任何这种类型的提示或任何东西,最终你的代码无论如何都会被转换成 JS。 如果你想要浏览器兼容性,你可以编写 ES6 并使用 Babel 进行转换。

我认为实际上,即使您可以为这些好处中的一些打上一巴掌,但一旦将其转换为 js,您就无法获得 java 风格的final任何真正好处。

@hk0i我想我能理解你的观点,但总的来说,我同意但同时有点不同意你的意见。

我会说打字稿中的大部分代码都是为了调试/跟踪目的,以帮助程序员做好我想是“干净”的代码(请不要引用我的话说,我知道他们在这里有一些打字稿意大利面代码,哈哈)

大多数打字稿功能在转换为 JS 时大部分时间都不会被转换,因为它们不存在于 JS 中。

尽管当您运行时/编写代码时,它有助于查看代码中的错误并比使用 JS 更容易跟踪它(而且 HECK JS 可能很难跟踪错误,哈哈)

所以我认为 final 关键字的使用可能会被视为 API 用户知道的阻碍:哦,好吧,这个类不应该被扩展,因为它的扩展可能会引发奇怪的行为等......

Typescript 作为一种很好的语言,我发现自己很难过,因为它缺少一些重要的特性,比如“真正的”静态类关键字,这些特性阻止人们调用类构造函数但仍然能够访问它。

@hk0i我也不同意,打字稿的目的是构建代码并因此提供这样做的工具。 我们不必在 Java 中使用 final,我们通常在知道扩展类或方法可能导致引入副作用(或出于任何其他安全原因)时使用它。

依赖当前的 ES6 解决方案添加运行时可变性检查(比如使用 Object.seal 或 Object.freeze)会使您失去在键入代码时直接出错的好处(尽管使用此装饰器可以轻松完成)。

@TheColorRed是的……在打字稿中,我们区分了局部变量和类变量。

  • readonly用于类变量
  • const :对于任何其他变量

对于方法或类,它们都不是准确的。 覆盖一个方法并不意味着我们要重新定义它(想想 C++ 中的虚拟方法),我们只是定义了一个方法,它将在超级方法之前被调用(我们可以使用super调用)。

@niokasgami也许命名并不是一个真正的问题,因为我们仍然会理解 Object.seal 应用于……一个对象,同时将一个类和一个方法密封到结构本身。

特尔;博士:

  • preventExtensions 将是一个很好的显式名称,以防止扩展类
  • preventOverrides 将是一个很好的候选名称,以防止覆盖类
    但是: seal在 C# 中被广泛使用,更短并且具有相同的预期行为。

顺便说一句,我忘了提到还有Object.preventExtensions()
差异可以在这里看到

  • 总是可能的……谁想要只写变量?

更新和删除

  • 防止重新定义删除变量:只读用于类成员,const(对于任何其他)
  • 防止重新定义方法:没有什么可以阻止您(在类方法中) this.aMethod = ()=>{} 。 方法是否也应该是readonly以防止此类黑客攻击? delete this.aMethod

创造

  • 仅适用于对象或正如我们在这里谈论的结构: class作为定义对象成员的类,打字稿已经隐式地阻止了动态创建新属性......例如this.unKnownProperty = "something" ts将按预期引发编译时错误TS2339

延伸

  • 只适用于类,我们还没有对对象本身做任何事情......(与运行时的创建不同)。 这是finalseal相关的地方。 问题是如何与 ES6 的冻结、密封和preventExtensions保持一致? (如果我们必须)。
    扩展一个类,意味着我们防止从这个类创建另一个类。 然后我们可以读取它但不能删除它(谁删除了类)。 问题不在于“更新”列。 final类是否可更新? 什么是可更新的类? 我的猜测是是的……但是更新将是实现这个类(理解类的接口)? 实现另一个类应该总是可能的……但这与更新初始类无关。 嗯……也许他们只是在编写 c# 时遇到了同样的问题,并认为seal是好的关键字。

覆盖

  • 防止覆盖扩展类中的当前方法。 同样,在讨论定义(类中的方法)时,我们还没有覆盖任何内容。 因此,禁止创建、允许读取、允许更新(如果它也适用于方法,我们可以使用 readonly 阻止更新或删除方法)。 最好的命名? preventOverrides ? 或再次seal因为它在 C# 中使用

奖励:由于密封方法通常是为了防止人们覆盖强制性的超级代码,因此我建议强制人们调用超级方法(与构造函数的行为相同,但对于任何方法),如果您愿意,请毫不犹豫地投票支持认为它会很有用。

不要误会我的意思,我不反对。 我非常喜欢它。 我只是想支持微软关于为什么它不应该成为一个功能的矛盾心态,这基本上是说因为它不是 JavaScript 功能,所以它不能成为 TypeScript 功能。

...如果我理解正确。

@hk0i泛型、只读等很多特性都不是 JavaScript 的特性,但它们仍然被添加到 TypeScript 中,所以我认为这不是原因......

TypeScript 只回避需要以非直接方式生成代码的功能。 泛型、只读等纯粹是编译时概念,可以简单地擦除以生成生成的输出,因此它们是公平的游戏。

final也可以在编译时“擦除”,那为什么不让它成为“公平游戏”呢?

🤷‍♂️

@TheColorRed我认为这或多或少是他们有其他优先事项需要整合。
或者他们不确定这个新关键字在编译/编码时间上的可预测性如何。 如果您考虑一下,final 关键字的设计可能非常模糊。 final 可以用于许多目的。 正如您从 Jsdoc 文档中看到的,您可以使用标签“@final”,它告诉 jsdoc 解释器该变量已冻结,因此是只读的。

这里是设计冲突。 Readonly 适用于变量和 getter,如果我记得哪个“冻结”了变量并使其成为只读,而最终使它成为最终的,因此也是只读的。

这可能会给不确定是否应该将变量标记为 final 或 readonly 的程序员造成混淆。

除非我们专门为类和方法声明保留这个关键字,否则我认为行为或最终会与只读发生冲突。

我认为,我们可以采用更“直接”的方式来命名和设计它,而不是 Sealed(可能与 object.seal 冲突)或 final(此目的设计与 readonly 关键字冲突)。

请注意这仅仅是一个与 C# 行为相似但同时不同的行为的“想法”? 我从 C# 通常是 nonOverridable 的方式中获得了一些灵感,然后你不得不说这个方法是虚拟的。

` 命名空间示例{

export class myClass {

    constructor(){}

    // Make the func non ovveridable by inheritance. 
    public unoveridable myMethod(){

    }

    public myMethodB(){

    }
}

export class MyClassB extends myClass {

    // Nope not working will throw an error of an nonOverridable error
    myMethod(){
        super.myMethod();
    }

    // Nope will not work since unoverridable.
    myMethod(){

    }

    // Design could be implemented differently since this could complicate  ¸
    // the pattern of typescript
    public forceoverride myMethod(){
      // destroy previous pattern and ignore the parent method thus a true ovveride
    }

    // Yup will work since it's still "virtual".
    myMethodB(){
        super.myMethodB();
    }
}
// Can extend an existing Parent class
// Declaring an unextendable class make all is component / func nonOverridable by 
// inheritance. (A class component can still be overwritten by prototype usage.)
export unextendable class MyFinalClass extends Parent {

    constructor()

    // nonoverridable by default.
    public MyMethod(){

    }
}
// nope throw error that MyFinalClass is locked and cannot be extended
export class MyChildClass extends MyFinalClass{}

}`

编辑:我没有包含 Variable 并且 get 因为 readonly 已经存在。

@mhegazy请考虑重新打开此问题。 社区的反应似乎压倒性地支持添加final关键字,例如 Java ( final )、C# ( sealedreadonly )、PHP ( final )、Scala ( finalsealed ) 和 C++ ( finalvirtual ) 都有。 我想不出没有这个特性的静态类型的 OOP 语言,我也没有听到关于为什么 TS 不需要它的令人信服的论据。

@bcherny @mhegazy我完全同意上述final不能只是另一个编译时约束的理由,就像我们在 TypeScript 中看到的大多数语法糖一样——让我们面对现实吧,静态类型、泛型、访问修饰符、类型别名,接口......所有的糖! 我知道 TypeScript 团队可能想专注于在其他方向上开发语言,但说真的,这是 OOP 101……这应该是很久以前的事了,那时 TypeScript 还很年轻!

@mhegazy我可以向您推荐您对此问题发表的早期评论吗?

具有私有构造函数的类是不可扩展的。 考虑改用这个。

在撰写本文时,此评论已被否决 21 次。
显然,这不是社区想要的解决方案!

哇,得到了很多关于这个的反馈。 我不明白为什么没有兴趣实施

这里发生了很多非常没有成效的抱怨。 请不要只是表现出沮丧的声音 - 我们可以在这里做出决定,社区可以提供反馈,但只是无所事事的咬牙切齿不会改变任何人的想法。 请具体且可操作; 我们不会被人们只是出现说现在做这个功能所说服。

请注意,这个问题是关于final/sealed classes 的。 有关于最终/密封方法的题外讨论,这是一个完全不同的概念。

我们上次回顾时考虑了一些要点

  • 类是 JAVASCRIPT 功能,而不是 TYPESCRIPT 功能。 如果你真的觉得没有final课程完全没用,那就是与 TC39 委员会讨论或带到 es-discuss 的事情。 如果没有人愿意或能够说服 TC39 final是必备功能,那么我们可以考虑将其添加到 TypeScript 中。
  • 如果在某些东西上调用new是合法的,那么它与从它继承的运行时对应关系是一对一的。 JavaScript 中不存在可以new 'd 但不能super 'd 的概念
  • 我们正在尽最大努力避免将 TypeScript 变成 OOP 关键字汤。 JavaScript 中有许多比继承更好的组合机制,而且我们不需要拥有 C# 和 Java 所拥有的每一个关键字。
  • 您可以简单地编写一个运行时检查来检测“应该是最终”类的继承,如果您“担心”某人意外继承您的类,那么您确实应该这样做,因为并非所有消费者都可能使用 TypeScript .
  • 您可以编写 lint 规则来强制执行某些类不是从其继承的风格约定
  • 有人会“意外地”从您应该密封的类继承的情况有点奇怪。 我还认为,某人对是否“可能”从给定类继承的个人评估可能相当可疑。
  • 密封一个类主要有三个原因:安全性、性能和意图。 其中两个在 TypeScript 中没有意义,因此我们必须考虑在其他运行时中只完成 1/3 工作的东西的复杂性与收益。

您可以自由地反对所有这些,但请提供一些可操作的具体反馈,说明缺少final损害您有效使用 JavaScript 类的能力。

还没有听到关于为什么 TS 不需要它的令人信服的论据

只是提醒一下,这不是它的工作方式。 所有功能都从 -100 开始。 从那个帖子

在其中一些问题中,这些问题会询问我们为什么“删除”或“删除”某个特定功能。 这个措辞意味着我们从现有的语言开始(C++ 和 Java 是这里的流行选择),然后开始删除功能,直到达到我们喜欢的程度。 而且,尽管有些人可能很难相信,但这不是语言的设计方式。

我们有一个有限的复杂性预算。 我们所做的每一件事都会让我们接下来做的每一件事都慢 0.1%。 当您请求一个功能时,您要求所有其他功能变得更难、更慢、更不可能。 没有任何东西进入这里,因为 Java 拥有它,或者因为 C# 拥有它,或者因为它只有几行代码,或者您可以添加命令行开关。 功能需要为它们自己以及它们对未来的全部负担付出代价。

@RyanCavanaugh虽然我不确定我是否同意这个决定,但我非常感谢您真正周到和详细的回复,以及您花时间写的。 我知道您希望保持语言精益 - 最好反思一下其他流行语言所具有的功能是否真的有价值,或者只是很常见。

@瑞安卡瓦诺

我不认为这里的重点,在这个论坛中是..“有效地使用 JavaScript 类”。

对不起,我无法抗拒,只是添加到“这里发生的无意义的抱怨”:final 关键字对于所有详细解释的原因以及其他人的论点都很有用。

我们正在尽最大努力避免将 TypeScript 变成 OOP 关键字汤。 在 JavaScript 中有许多比继承更好的组合机制......

就像当你使用final在继承中强制组合(在此处插入不是打字稿语言名称)😈
(对不起,我无法抗拒)

但更重要的是,TS 似乎主要是一个兼容层,以引入“今天的 JavaScript”或类似的东西(a la Babel),但它添加了一些额外的类型检查。

我认为实现这一点的反驳论点的要点是,如果您想要另一种语言的功能,请改用另一种语言。 如果您需要 JavaScript,请使用 JavaScript。 如果您想要 TypeScript,请使用它。

我知道 Kotlin 可以编译为 JS 并且有很多我认为这里的人会喜欢的功能,所以这可能是有用的研究。 我认为它确实添加了一些 JS 库作为依赖项(我自己没有尝试过)。

主要的收获是,如果您对 TypeScript 不满意,请不要使用它。

@瑞安卡瓦诺

关于你上面提到的几点......

类是 JAVASCRIPT 功能,而不是 TYPESCRIPT 功能。 如果你真的觉得没有final课程完全没用,那就是与 TC39 委员会讨论或带到 es-discuss 的事情。 如果没有人愿意或能够说服 TC39 final是必备功能,那么我们可以考虑将其添加到 TypeScript 中。

我从 0.7 开始就一直在关注 TypeScript,那时,IIRC 的目标是 ES3-ES5。 那时,类绝对是 TypeScript 的一个特性。 同样的论点也适用于装饰器——它们在 ES 的卡片上,但它们已经作为实验性功能出现在 TypeScript 中。

作为一个社区,我们并不认为 TypeScript 编译器必须以某种方式发出一个final的 JavaScript 类。 编译时检查就足够了,就像编译时检查访问修饰符就足够了一样; 毕竟,它们也不是 JavaScript 特性,但 TypeScript 仍然使它们成为可能。

如果final确实成为了 ES 功能,那么大概您可以在针对 ES* 时重构发射器的那一点, final适当地输出

如果在某些东西上调用new是合法的,那么它与从它继承的运行时对应关系是一对一的。 JavaScript 中不存在可以new 'd 但不能super 'd 的概念

再说一次,我不认为社区希望您在这里使用魔法来在运行时强制执行final 。 正如您准确地指出的那样,它“在 JavaScript 中不存在”,但如前所述,编译时约束就足够了。

我们正在尽最大努力避免将 TypeScript 变成 OOP 关键字汤。 JavaScript 中有许多比继承更好的组合机制,而且我们不需要拥有 C# 和 Java 所拥有的每一个关键字。

在整个软件开发社区中,这是一个相当广为人知的短语:“偏爱组合胜过继承”,但是“偏爱”并不意味着不要将继承用作一揽子声明。 当然,您不需要 C# 和 Java 拥有的每一个关键字,但是您必须有一个允许abstract类的理由(顺便说一下“在 JavaScript 中不存在”),那么为什么不呢? final课程?

您可以简单地编写一个运行时检查来检测“应该是最终”类的继承,如果您“担心”某人意外继承您的类,那么您确实应该这样做,因为并非所有消费者都可能使用 TypeScript .

C# 和 Java 也可以这样说,但我不需要运行时检查,因为我有sealedfinal在编译器级别为我做这件事。

如果不是我的所有消费者都在使用 TypeScript,那么我要担心的不仅仅是从final类继承; 例如,访问privateprotected成员,没有静态类型检查,或构造abstract类的能力。

为了确保我们的非 TypeScript 消费者尽可能安全,我们的代码是否真的应该对所有这些事情进行运行时检查(其中一些我什至不可能添加)?

您可以编写 lint 规则来强制执行某些类不是从其继承的风格约定

这意味着使用你的代码的人正在运行 linter,所以它仍然不是一个真正合适的解决方案; 这是一种解决方法。

有人会“意外地”从您应该密封的类继承的情况有点奇怪。 我还认为,某人对是否“可能”从给定类继承的个人评估可能相当可疑。

优秀/伟大的开发人员会考虑他们做某事的理由,比如扩展一个类。 任何比这少的,你最终都会有一种“但它有效”的心态,而没有真正理解为什么。

问一个以can I开头的问题通常比以should I开头的问题有更明确的答案。

密封一个类主要有三个原因:安全性、性能和意图。 其中两个在 TypeScript 中没有意义,因此我们必须考虑在其他运行时中只完成 1/3 工作的东西的复杂性与收益。

可以说,我会说 TypeScript 实际上会满足以下两个原因:安全性和意图,但是如果这纯粹是作为编译时检查来实现的,那么它只能在编译器级别执行此操作,而不是像您正确指出的那样, 在运行时级别。

我不认为没有final会损害我有效使用 JavaScript 类的能力,如果没有它,JavaScript 类也不会无法使用。 我觉得final更像是一个“不错的”功能,而不是“必要”的功能,但是对于 TypeScript 的许多功能来说也是如此。

我认为这里的主要抱怨是 TypeScript 允许我们创建abstract和 _open_ 类,但是在继承和多态性方面,它不允许我们以优雅和富有表现力的方式绘制完整的图片。

隐藏构造函数不是社区想要的,因为这会从类实现中带走语义和表达价值,而且再一次,因为访问修饰符是“很高兴拥有”,这甚至不能在运行时强制执行; 本质上是“在 JavaScript 中不存在”的情况。

作为一名开发人员,我有很多帽子; 目前我有一个 C# 帽子、一个 Kotlin 帽子和一个 TypeScript 帽子。

  • 戴上 Kotlin 帽子后,我不必担心final因为默认情况下类是 final 的。
  • 带着我的 C# 帽子,我习惯性地应用sealed ,迫使我的消费者问can I而不是问should I 。 通常情况下,C# 类实际上应该是sealed并且它经常被忽视。
  • 戴上 TypeScript 帽子后,我必须隐藏构造函数作为解决方法,因为在我(和社区)看来,该语言缺乏有用的东西。

出于兴趣,当 TypeScript 团队围坐在桌子旁讨论abstract类时,有没有人举手说“但是final怎么样”?

@RyanCavanaugh嗯,我理解你的观点

我认为这主要是一种误解(我猜我们是如何解释的)

@series0ne总结得很好,我们的意图并没有通过要求在 JS 中创建 final 类来使打字稿输出过于复杂(哎呀,这样做会造成火车失事),而只是创建额外的规则来告诉人们:不,你不能'不要那样做。 并且它告诉 API 的用户,他们可能不应该在打字稿文件/编译器上而不是在输出上混淆这些类(出于稳定性、安全原因等)。

我认为拥有这个功能会很好吗:是的,有更快速的方法来做一些技术很好。

真的需要吗? 如果 Typescript 团队能够负担得起实现这个额外功能,那么这可能不是一个考虑周到的小问题,并且不应该成为优先考虑的事情,那么拥有它可能会很好。

我确实看到 final/sealed 关键字可能会使很多方面复杂化,尤其是:错误修复、补丁、行为改变。

如果我解释一下,我可以完全看到通过锁定所有类不被扩展来滥用 final。 给 IMO 带来巨大的痛苦来应对。 我不想使用我被锁定无法通过继承对用户类进行任何更改的 API,因为它们因为“安全性”而变得偏执。

这意味着我将不得不使用原型然后使用别名或覆盖这个类,只是为了能够改变这个类的行为以满足我的需要。
这会很烦人,特别是如果我只想为上述类添加额外的功能并且不想使用破坏性的工作流程。

在这之后,虽然我可能会误解自己现在的情况,所以请随意向我解释:)

啊,如果我记得我读了很多关于 ES4 的书(我不确定当草稿在制作时我还是个孩子,哈哈)而且令人惊讶的是它看起来与 Typescript 非常相似。

它在此处的规范草案中说:
https://www.ecma-international.org/activities/Languages/Language%20overview.pdf

A class that does not explicitly extend any other class is said to extend the class Object. As a consequence, all the classes that exist form an inheritance hierarchy that is a tree, the root of which is Object. A class designated as final cannot be extended, and final methods cannot be overridden: class C { final function f(n) { return n*2 } } final class D extends C { function g() { return 37 } }

所以从技术上讲,最终关键字被计划到 Ecmascript 第 4 版中。 我们是否会在未来的 JS 集成中看到它,我对此表示怀疑,但它们确实集成了类,所以这可能是可能的,但谁知道呢?

我有一个用例,我几乎没有遇到过,这促使我找到了这个问题。 它围绕使用this作为返回类型:

abstract class TileEntity {
    //constructor, other methods...
    abstract cloneAt(x: number, y: number): this;
}

我写这篇文章的目的是确保 TileEntities 是不可变的(因此可以安全地传递),但任何给定的 TileEntity 都可以使用一些更改的属性(在这种情况下xy ) 而不必知道子类的形状。 这就是我想写的,但是尝试(正确地)编写实现会导致它中断。

class TurretTileEntity extends TileEntity {
    //constructor, other methods...
    cloneAt(x: number, y: number): this {
        return new TurretTileEntity(x, y, /* ... other properties */);
    }
}

问题是,如果无法声明 _永远_ 不会是 TurretTileEntity 的子类,则this返回类型不能缩小到仅 TurretTileEntity 类型。

考虑到您可以将返回值转换为<any> ,Typescript 中的这个缺点的影响很小。 客观地说,这是一种反模式,也是一个有用的迹象,表明类型系统可以更具表现力。 应支持最终/密封类。

出于兴趣,当 TypeScript 团队围坐在一张桌子讨论抽象类时,有没有人举手说“但是 final 呢”?

是的。 每次我们添加一个关键字时,“但是我们也必须添加另一个关键字!” 是反对添加它的强项,因为我们希望尽可能小心地发展语言。 类声明上的final也在这个阵营中 - 如果我们有final类,那么我们也将“需要” final方法或属性。 任何看起来像范围蠕变的东西都会被极端怀疑。

我认为 final 类和方法非常重要。 我希望你能实施它。

请注意,此问题与最终/密封类有关。 有关于最终/密封方法的题外讨论,这是一个完全不同的概念。

mhegazy 关闭了https://github.com/Microsoft/TypeScript/issues/9264以支持此问题。 是否有一个单独的问题没有被关闭作为跟踪最终/密封方法的欺骗?

@crcla 通过阅读上面@RyanCavanaugh的一些评论,我相信关闭另一个问题的理由是因为支持 final/sealed 类包括支持 final/sealed 方法。 两者是非常相关的话题。

我不敢相信final的提议被标记为Won't fix

请把标签改回In Discussion并执行它!

就我而言,我想为插件编写一个抽象基类,并且我想确保插件类不会错误地覆盖我的基类方法。 private constructorObject.seal都不能完成这项工作。

对于那些认为这是一个关键的、生死攸关的功能的人来说:它真的不是。 如果您的 API 包括公开需要由使用者扩展的类实现,那么有更好的方法来做到这一点。 不要效仿 React 的坏榜样。 只需使用函数和简单的对象,并将这种面向对象的废话留在它所属的地方:过去。

现在开始投反对票😀

有人能解释一下这个讨论如何在 final 类和 final 方法之间没有区别吗? 当然,防止某些方法被覆盖会增加巨大的价值。 我想为插件开发人员提供通过子类化我的一些框架类并实现抽象函数来添加功能的可能性 - 但同时确保她没有(为什么!)覆盖调用这些抽象函数的方法(和确保发生错误处理/登录/检查。

@pelotom如果您不想使用 OOP 构造和模式,请不要使用,没有人会告诉您不这样做,但是同样应该适用于相反的方向:如果有人想使用 OOP 构造,就让他们使用。
通过强迫其他人采用特定的(非 OOP)模式,您将采用与您谴责 OOP 的行为相同的行为。 至少保持一致:)

final关键字添加到 TypeScript 并不是一项重大更改,也不会以任何方式影响您现有的任何项目,但是对于@thorek 、我自己和许多其他人

如果有人想使用 OOP 结构,就让他们使用。

我们不是在争论人们是否应该使用 OOP 结构,我们是在争论是否应该将当前不存在的 OOP 结构添加到语言中。 这样做会占用开发人员本可以花在其他功能上的时间,并使语言变得更加复杂,这会减慢未来功能的速度(因为对于每个新功能,必须考虑它将如何与每个旧功能交互)。 所以它肯定会影响我,因为我宁愿将 TypeScript 的开发和复杂性预算花在更有用的事情上!

fwiw,我们可能会鼓励我们的客户(谷歌)在评论中使用@final以便它传播到闭包编译器:\

当研发团队构建其他团队可以使用的 TS 库时,在类和方法上添加 _final_ 很有用。 这些关键字是此类工具服务公开稳定性的(重要)部分。
关键字对大团队更重要,对小项目不太重要,恕我直言。

@dimiVergos正是我的情况,我认为其他许多人也是如此。 我可以想象,由于我的立场,我可能看起来有偏见,但根据我的经验,这似乎是唯一(尽管高度)合理使用此关键字的方法。

我愿意对此进行更正!

与最终类相比,我更关心支持 final 方法(以避免子类意外覆盖它们)。 是正确的票吗? 票证https://github.com/Microsoft/TypeScript/issues/9264似乎暗示如此。

那么我如何投票支持“最终方法”? 还是我只是通过此评论投票支持它? 另外我们如何在标题中添加“方法”? 我可以直接编辑吗? 当前标题误导性地仅限于类,但讨论也包括方法。

像许多其他人一样,我希望看到 TypeScript 中的最终方法。 我已经使用以下方法作为部分解决方法,它不能避免失败,但至少有一点帮助:

class parent {
  public get finalMethod(): () => void {
    return () => {
      // final logic
    };
  }
}

class child extends parent {
  // ERROR "Class 'parent' defines instance member accessor 'finalMethod', but extended class 'child' defines it as instance member function"
  public finalMethod(): void { }
}

这是final方法的另一个真实用例。 考虑以下React.Component扩展,以便更轻松地实现简单的Context

interface FooStore{
  foo: string;
}

const {Provider, Consumer} = React.createContext<FooStore>({
  foo: 'bar';
});

abstract class FooConsumerComponent<P, S> extends React.Component<P, S> {

  // implement this instead of render
  public abstract renderWithStore(store: FooStore): JSX.Element;

  public final render() {
    return (
      <Consumer>
        {
          (store) => {
            return this.renderWithStore(store);
          }
        }
      </Consumer>
    );
  }
}

如果final render()方法上没有final ,则无法阻止错误地覆盖该方法,从而破坏一切。

嗨,像许多其他人一样,至少对于方法,我想要“最终”或“密封”关键字。 我完全同意这个关键字是一个真正的附加值,因为它允许开发人员阻止他们的图书馆客户扩展关键代码块(例如在插件中)。

我发现的唯一解决方法是使用方法装饰器,如:
function sealed(target, key,descriptor){ descriptor.writable = false; // Prevent method to be overriden }

然后在您的“基”类中,使用装饰器来防止方法覆盖,例如:

class MyBaseClass {
<strong i="10">@sealed</strong>
public MySealedMethod(){}

public OtherMethod(){}

...
}

这样“MySealedMethod”就不能(很容易)在子类中被覆盖。 例子:

class AnotherClass extends MyBaseClass {
public MySealedMethod(){} ====>> ERROR at RUNTIME (not at compile time)
}

实际上,此解决方法的唯一缺点是“密封”仅在运行时可见(javascript 控制台中的错误),而不是在编译时可见(因为方法属性是通过我猜的函数设置的)

@瑞安卡瓦诺

是的。 每次我们添加一个关键字时,“但是我们也必须添加另一个关键字!” 是反对添加它的强项,因为我们希望尽可能小心地发展语言。

在我看来,你开始这个论点时,你的想法是针对该功能的。 流行的功能不应该成为反对考虑这样的功能的理由。 应该问自己_为什么它受欢迎_,然后仔细考虑它。

我相信你已经讨论过它并且争论已经围绕着考虑它或反对它。 反对它的论点是它的功能蠕变,还是有什么更具体的? 你认为它是一个边缘功能吗? 如果是这样,我们可以做些什么来改变你的想法吗?

让我把这些问题留在这里供大家思考。

有没有一种方法可以使用现有构造_通过设计_来密封类?
如果添加了该功能,它会允许我们做任何新的事情吗? (例如,现有结构不允许的东西)
如果该功能确实允许我们做一些新的事情,它是否有价值?
添加密封类会增加语言的复杂性吗?
添加密封类会增加编译器的复杂性吗?

为了讨论这些,我没有在讨论中包括密封方法,也没有在实现中包括运行时检查。 我们都知道我们无法在 Javascript 上强制执行此操作。

仅供参考:如果此功能的反击不仅仅是针对功能蠕变的全面声明,我会很棒。

@asimonf我认为您所说的所有内容此评论解决(您需要展开“显示更多”部分才能看到它)以及它后面的内容。

什么会改变我们的想法? 人们展示的代码示例

这里关键字试图解决的情况是有人错误地从类继承。 你如何错误地从一个类继承? 这不是一个容易犯的错误。 JavaScript 类本质上很脆弱,任何子类都必须了解其基类的行为要求,这确实意味着编写某种形式的文档,并且该文档的第一行可以简单地是“不要子类化此类”。 或者可以进行运行时检查。 或者类构造函数可以隐藏在工厂方法后面。 或任何其他数量的选项。

当情况已经是你应该遇到至少两个单独的障碍告诉你不要在那里开始时,为什么 TypeScript 必须有这个关键字?

我已经编程了大约多年了,我从来没有尝试过对某些东西进行子类化,却发现它是密封的,我不应该尝试。 不是因为我是超级天才! 这只是一个极不常见的错误,在 JavaScript 中,这些情况已经是您首先需要询问基类问题的情况。 那么正在解决什么问题呢?

我将使用 final 的一个真实案例是防止用永远不会调用超级方法的东西覆盖一个方法。 当然是一些公共方法。 但我想出的真正解决方案是能够强制任何子方法调用超级方法。 https://github.com/Microsoft/TypeScript/issues/21388

由于我不是语言设计师,我无法谈论向语言添加关键字的缺点。 我会说我目前正在研究一个抽象超类,它有一个我打算成为 final 的方法,但令我沮丧的是我发现我不能这样做。 现在,我将采用不太理想的发表评论的方法,但实施该课程的团队成员可能不会阅读所述评论。

我认为上面有很多很好的例子说明final何时/何地不仅有用,而且为正在构建的平台提供_真实价值_和_真实安全_。 对我来说没有什么不同。 正如我所说,我对语言设计的复杂性知之甚少,但这显然是人们想要/将使用的功能/我真的看不到滥用它的方法或它会生成不良代码。 我理解担心范围蔓延的概念,但 TS 开发人员不必实现社区想要的一切。

只是为了在辩论中添加我的一点,我想把我的拇指放在规模的yes-side 上,因为这个问题充满了支持添加该功能的好论据,但我看到的反对论点是:

  • 它可能导致范围蔓延(这是可以管理的)
  • 人们会要求其他功能(好吧,但谁在乎,这是设计语言的一部分,这不是我们正在设置的法律先例)
  • 它可能会降低效率(我想,由于 TS 团队充满了坏蛋,您可能会找到一种方法来使其易于管理)
  • 自以为是的“不要使用 OOP 概念”评论

以上似乎都不是对 TS 生态系统本身的可信威胁,也不是由此产生的代码; 它们似乎都是政治性的(也许效率除外,但我对你们有很大的信心,所以我并不真正担心,再加上轻微的效率下降不值得因不知情的继承错误而炸毁重要的组件/库结构) .

希望这是建设性的! 喜欢 TS 并希望看到它不断改进!

👍

- 编辑 -

回应@RyanCavanaugh

So what problem is being solved?

我认为正在解决的问题由上面的许多评论概述。 我并不是说这是一种侮辱,但这个问题确实听起来,对我来说,就像你实际上并没有关注所提出的论点,而且你的想法是“不”。 我的意思不是不尊重,只是老实说,这个线程充满了可以解决的问题的例子。

不知何故,我错过了这个评论,它很好地概述了添加功能的严重问题,这对我来说很有意义。 同样,我不知道添加它的复杂性,但除了效率问题(因为开发人员不必担心 TS 效率,这是 TS 团队的工作),似乎没有任何反对 final 的好论据(以我最谦虚的观点)。

最近的大部分讨论都集中在 final方法上(对 final 类的讨论较少)。 最终方法当然是我的主要兴趣。 我刚刚注意到,如果您扩展@RyanCavanaugh 上一篇文章中的“此评论”链接,他会认为最终方法讨论偏离主题。 然而@mhegazy建议这张票是正确的地方(当关闭 #9264 时)。 因此,作为一个低级的最终用户,我对我应该在哪里要求/投票最终方法感到困惑。 指导将不胜感激。

@cbutterfield
矛盾是我也注意到的,但由于害怕咆哮而没有在我的帖子中指出(我实际上通过 #9264 找到了这个线程,并在@mhegazy的指导下来到这里)。
@瑞安卡瓦诺
@mhegazy
根据上面的评论,其中_是_讨论最终方法的正确位置,因为这实际上是我最感兴趣的事情。

回顾我被严重低估的评论(如果你还没有,请务必添加你的👎!)我很高兴地报告说 React _不再_通过强制使用基于class的 API 来树立一个坏榜样在它的用户! 通过 Hooks 提案,React 团队意识到并接受了一种从根本上更简单的函数式方法。 有了这个,许多开发人员不得不使用类的最后一个原因已经消失了,并且摆脱了困境。 我要重申:你认为需要上课的一切都可以在没有它们的情况下做得更好

JS 和 TS 的未来是无类的,赞!

@cbutterfield我也注意到了,如果问题 #9264 被关闭以在本期讨论,我认为标题应该更改为Suggestion: Final keyword for classes and methods ,否则寻找最终方法的人(仅)可能不会在第一个帖子。

引用@mhegazy
Java 和/或 C# 使用final类在运行时优化您的类,知道它不会被专门化。 我认为这是最终支持的主要价值。 在 TypeScript 中,没有任何东西可以让您的代码运行得比没有 final 时更好。

我根本不同意。 我在许多团队中使用 Java 开发多年,我们从未使用过 final 类或方法进行运行时优化; 在实践中,它主要是一个面向对象的设计习惯用法。 有时我只是不希望我的类或方法被外部子类扩展/覆盖,无论是为了保护和保证功能,还是将库的 API 噪音限制在基本要素上,或者是为了保护 API 用户免于误解复杂的 API。

是的,这些问题可以通过添加注释或接口来解决,但是final是一个优雅的解决方案,可以将可见的 API 减少到必要的功能,而您只需要一个带有干净可扩展 API 的简单类。

尽管这个问题已经有将近 3 年的历史了,但我们真的应该有一个final关键字,因为已经有一个abstract关键字。

请记住,我们不想强迫人们使用它,它与 abstract 关键字一样有用。 但这是另一个用例,它将强烈显示final关键字的好处:

abstract class A {
  protected abstract myVar: string;

  protected abstract myFunction(): void;
}

class B extends A {
  protected readonly myVar: string = "toto";

  constructor() {
    super();
    this.myFunction();
  }

  protected myFunction() {
    console.log(this.myVar);
  }
}

class C extends B {
  constructor() {
    super();
  }

  protected myFunction() {
    console.log("tata");
  };

  public callFunction = () => {
    this.myFunction();
  }
}

const myB = new B(); // toto

const myC = new C(); // toto
myC.callFunction(); // tata

编译后结果:

toto
tata

所以在这段代码中,我们有一个抽象类A ,它有抽象方法和属性,我们希望继承的类定义它们,但我们不想让任何其他类覆盖这些实现。
我们能做的最好的事情是保留protected关键字,但如您所见,我们仍然可以重新定义属性。

那么,如果我们在 Typescript 编译过程中更进一步并使用readonly来保护我们的属性(并假装我们的方法是属性)呢?

class B extends A {
  [...]

  protected readonly myFunction = () => {
    console.log(this.myVar);
  }
}

class C extends B {
  protected myVar: string = "I am not protected that much";
  [...]

  protected myFunction = () => { // using the readonly keyword doesn't prevent any modification
    console.log("tata"); // If you launch this code, tata will still be logged in the console.
  };
}

(注意代码是用tsc编译的,编译或执行都不会抛出错误)
所以现在我们有两个问题:

  • 我们不保护我们的B属性,所以我们需要一种方法来防止任何意外的继承。
  • 现在我们知道我们可以通过扩展它的类来覆盖任何readonly属性,是的,Typescript 知道它是我们正在更改的父属性,因为使用private而不是protectedC类中会抛出错误,因为它不是相同的访问类型/可见性。

目前,我们可以使用装饰器(如官方 Typescript 文档所示的密封最终装饰器),但请记住它仅在运行时有用,我们必须在编译过程中防止这种情况。

当我们想要覆盖受保护的只读值时,我会查看是否存在有关“安全漏洞”(如果我们可以称之为)的问题,而我们实际上可以也不应该这样做。 否则,我将就privateprotected关键字与继承内容之间的readonly关键字的验证展开辩论。

; 博士: final关键字将解决两个问题(尽管readonly验证将是防止任何未经授权的重写的良好开端)

三年后……太荒谬了。

为了证明这个特性的必要性,我建议看看其他语言是怎么做的:

Java:
final class SomeClass { ... }
PHP:
final class SomeClass { ... }
C#:
sealed class SomeClass {...}
C++:
class SomeClass final { ... }
Delphi:
type SomeClass = class sealed() ... end;

所以,我们看到在所有最流行的 OOP 语言中都存在这个特性,因为它来自 OOP 的继承逻辑。

另外我必须引用 TS 的开发负责人说

如果我们有 final 类,那么我们也将“需要”final 方法或属性。

我不确定这是否具有讽刺意味,但在 JS 中, readonly 关键字用于属性(如果你作弊,还有方法),所以这是一个非常愚蠢的观点。

并且也许不让一个人在他有 +40 次投票时关闭问题意味着社区强烈反对他,这将是避免进一步愚蠢情况的好建议。

如果你对加强 Typescript 的主要功能不感兴趣,请大声说出来,以便科技新闻可以在 Twitter 上转发,否则,这只是一个隐藏的坏嗡嗡声。 你只是忽略了你的社区,而且你没有让社区验证我们需要或不需要的过程。

也许不让一个人在他有 +40 次投票时关闭问题意味着社区强烈反对他将是一个很好的建议,以避免进一步的愚蠢情况

我不是“一个人”。 当我在这里做决定时,是整个 TypeScript 团队决策过程的反映。 设计团队多次看了看这个问题,每次都得出了同样的结论。 欢迎您不同意该结果,但该过程无需辩论。

你没有让社区验证我们需要或不需要的过程。

就是过程,就在这里:你称我对我们推理的总结是“愚蠢的”,而我(和团队中的其他人)正在阅读它。 我们正在听取反馈。

我知道指出其他已实现的功能作为考虑这个功能的原因是一种气味而不是真正的论点,但我发现考虑并添加了类型别名机制具有讽刺意味(它可能很棒,但没有它当然可以生活) 但是这样的事情被拒绝了。

最后,我可以没有这样的功能,但对于 TS 具有的大约一半的功能,可以说同样如此。 那么,考虑实施这一点的适当理由是什么?

GitHub 在这里隐藏了很多评论,所以我重新发布了我们过去给出的各种回复(加上一些补充)。 在下面添加一些想法。


我们上次回顾时考虑了一些要点

  • 类是 JAVASCRIPT 功能,而不是 TYPESCRIPT 功能。 如果你真的觉得没有final课程完全没用,那就是与 TC39 委员会讨论或带到 es-discuss 的事情。 如果没有人愿意或能够说服 TC39 final是必备功能,那么我们可以考虑将其添加到 TypeScript 中。
  • 如果在某些东西上调用new是合法的,那么它与从它继承的运行时对应关系是一对一的。 JavaScript 中不存在可以new 'd 但不能super 'd 的概念
  • 我们正在尽最大努力避免将 TypeScript 变成 OOP 关键字汤。 JavaScript 中有许多比继承更好的组合机制,而且我们不需要拥有 C# 和 Java 所拥有的每一个关键字。
  • 您可以简单地编写一个运行时检查来检测“应该是最终”类的继承,如果您“担心”某人意外继承您的类,那么您确实应该这样做,因为并非所有消费者都可能使用 TypeScript .
  • 您可以编写 lint 规则来强制执行某些类不是从其继承的风格约定
  • 有人会“意外地”从您应该密封的类继承的情况有点奇怪。 我还认为,某人对是否“可能”从给定类继承的个人评估可能相当可疑。
  • 密封一个类主要有三个原因:安全性、性能和意图。 其中两个在 TypeScript 中没有意义,因此我们必须考虑在其他运行时中只完成 1/3 工作的东西的复杂性与收益。

还没有听到关于为什么 TS 不需要它的令人信服的论据

只是提醒一下,这不是它的工作方式。 所有功能都从 -100 开始。 从那个帖子

在其中一些问题中,这些问题会询问我们为什么“删除”或“删除”某个特定功能。 这个措辞意味着我们从现有的语言开始(C++ 和 Java 是这里的流行选择),然后开始删除功能,直到达到我们喜欢的程度。 而且,尽管有些人可能很难相信,但这不是语言的设计方式。

我们有一个有限的复杂性预算。 我们所做的每一件事都会让我们接下来做的每一件事都慢 0.1%。 当您请求一个功能时,您要求所有其他功能变得更难、更慢、更不可能。 没有任何东西进入这里,因为 Java 拥有它,或者因为 C# 拥有它,或者因为它只有几行代码,或者您可以添加命令行开关。 功能需要为它们自己以及它们对未来的全部负担付出代价。


什么会改变我们的想法? 人们展示的代码示例

这里关键字试图解决的情况是有人错误地从类继承。 你如何错误地从一个类继承? 这不是一个容易犯的错误。 JavaScript 类本质上很脆弱,任何子类都必须了解其基类的行为要求,这确实意味着编写某种形式的文档,并且该文档的第一行可以简单地是“不要子类化此类”。 或者可以进行运行时检查。 或者类构造函数可以隐藏在工厂方法后面。 或任何其他数量的选项。

换句话说:在 JavaScript 中从类继承的唯一正确过程总是阅读该类的文档开始。 基类可能需要简单地派生并开始编码的约束太多。 如果在第一步,您应该已经知道不要尝试,那么在第三步中静态强制执行提供了什么价值?

当情况已经是你应该遇到至少两个独立的障碍告诉你不要在那里开始时,为什么 TypeScript必须有这个关键字?

我已经编程了大约多年了,我从来没有尝试过对某些东西进行子类化,却发现它是密封的,我不应该尝试。 不是因为我是超级天才! 这只是一个极不常见的错误,在 JavaScript 中,这些情况已经是您首先需要询问基类问题的情况。 那么正在解决什么问题呢?

如果你对加强 Typescript 的主要功能不感兴趣,请大声说出来,以便科技新闻可以在 Twitter 上转发

我们非常清楚我们如何设计语言; 非目标一号正好说明了这一建议。


阅读这里的许多评论后,我个人的反应是,由于其他语言中存在的结构,存在强烈的偏见效应。 如果你只是从一个空白的角度来看这个,你可以想象出几十种行为,其中 TypeScript 只有一小部分。

您可以很容易地想象某个关键字应用于方法并强制派生方法通过super调用它。 如果 C# 和 Java 有这个关键字,人们绝对会在有意义的地方应用这个关键字。 事实上,它可以说比final更普遍地执行,因为它不可能应用运行时检查,并且是基派生契约的一个比“派生类可能不存在”更微妙的方面”。 它在final不会有的各种方面都很有用。 我更愿意拥有那个关键字而不是这个关键字(尽管我认为两者都不符合复杂性与价值栏)。

那么,考虑实施这一点的适当理由是什么?

当我们查看反馈时,强烈的考虑是这样的:

  • 我无法充分表达这个 JavaScript 库的行为
  • 我的代码编译了,但真的不应该,因为它有这个(微妙的)错误
  • 类型系统未充分传达此代码的意图

final命中这些,但修饰符也会说“这个函数不能连续调用两次”或“这个类的构造直到下一个异步滴答声才真正完成”。 并非所有可以想象的东西都应该存在。

我们从未见过这样的反馈:“我花了几个小时调试,因为我试图从一个实际上不能子类化的类继承”——有人说这会正确地触发 'wat',因为它不是真的可以想象的。 即使修饰符存在,它也不会真正帮助这种情况,因为库不会记录类是否是最终的 - 例如fs.FSwatcher final ? 似乎连节点作者都不知道。 所以final如果作者知道它是final final就足够了,但是无论如何都会记录下来,并且缺少final并没有真正告诉你任何事情,因为它经常无论如何都不知道。

我阅读了整个区块并理解了团队关于功能选择的声明,我将从我的评论中回顾某些观点

我不是“一个人”。 当我在这里做决定时,是整个 TypeScript 团队决策过程的反映。

抱歉,我指的是 mhegazy 以及他从使用 Typescript 的社区获得的大量反馈。

如果您真的觉得没有 final 的课程完全没用,那可以与 TC39 委员会讨论或进行 es-discuss。 如果没有人愿意或能够让 TC39 相信 final 是一项必备功能,那么我们可以考虑将其添加到 TypeScript 中。

我不认为final是第一步,因为已经有关于private关键字的提案https://github.com/tc39/proposal-private-methods

我对You should a comment to say *dont do this*持怀疑态度,这就像在Hey don't write down specific characters表格上说,你几乎永远编码了多年,所以你应该知道开发人员的第 1 条规则:永远不要相信用户(以及将使用您的代码的开发人员)

我并不是说我们必须绝对停止一切并投入 10 亿美元来实现 final 关键字,因为用例太低而无法达到那样的效率,还要考虑到我给出了几个限制同时来自 TS 和 JS 的示例,并且如果人们选择 TS,那是为了防止编译时出错,而不是运行时出错。 如果我们想让事情在运行时爆炸,我们可以使用 JS 并停止关心 TS,但这不是重点,因为有一个最终用例展示了我如何使用final关键字:我想锁定一个方法,我不希望任何人覆盖它。

由于 Javascript 受此限制,社区认为 Typescript 可能会超出该限制,这就是为什么这个问题已经存在 3 年了,这就是为什么人们仍然想知道为什么这个功能不存在,这就是为什么我们希望编译器手动检查类和/或方法。

你没有等到 JS 有私有/公共方法来实现它们,尽管实际上有提议将它们包含在#关键字中(比public / private ),我知道你想创造一种完美的语言,因为完美不是你不能再添加任何东西,而是你不能再删除任何东西。

如果您能找到防止在编译过程中覆盖方法的解决方案(而不是在运行时过程中,因为该点无效),请成为我的客人。

我试图展示这种情况的弱点(在方法/属性上而不是在类上),因为任何 TS 开发人员都可以重写他们想要的任何库,破坏他们想要的任何东西,我猜也许这就是事情的美妙之处。

顺便感谢您的回复,没有针对开发团队或您的仇恨或不良行为。

所有有效积分! 但是,请让我们分开讨论

a) 支持 final 类
b) 支持最终方法

虽然您的所有论点都针对 a) - 没有针对 b。

正如在讨论中多次指出的那样,拥有最终方法至关重要。 到目前为止,我听到的唯一答案是“不要做 OOP”——但这不是我(和许多其他人)会同意的。

上午 28.01.2019 嗯 20:32 schrieb Ryan Cavanaugh通知@github.com:

GitHub 在这里隐藏了很多评论,所以我重新发布了我们过去给出的各种回复(加上一些补充)。 在下面添加一些想法。

我们上次回顾时考虑了一些要点

类是 JAVASCRIPT 功能,而不是 TYPESCRIPT 功能。 如果您真的觉得没有 final 的课程完全没用,那可以与 TC39 委员会讨论或进行 es-discuss。 如果没有人愿意或能够让 TC39 相信 final 是一项必备功能,那么我们可以考虑将其添加到 TypeScript 中。
如果在某些东西上调用 new 是合法的,那么它与从它继承的运行时是一一对应的。 JavaScript 中不存在可以是新的但不是超级的东西的概念
我们正在尽最大努力避免将 TypeScript 变成 OOP 关键字汤。 JavaScript 中有许多比继承更好的组合机制,而且我们不需要拥有 C# 和 Java 所拥有的每一个关键字。
您可以简单地编写一个运行时检查来检测“应该是最终”类的继承,如果您“担心”某人意外继承您的类,那么您确实应该这样做,因为并非所有消费者都可能使用 TypeScript .
您可以编写 lint 规则来强制执行某些类不是从其继承的风格约定
有人会“意外地”从您应该密封的类继承的情况有点奇怪。 我还认为,某人对是否“可能”从给定类继承的个人评估可能相当可疑。
密封一个类主要有三个原因:安全性、性能和意图。 其中两个在 TypeScript 中没有意义,因此我们必须考虑在其他运行时中只完成 1/3 工作的东西的复杂性与收益。
还没有听到关于为什么 TS 不需要它的令人信服的论据

只是提醒一下,这不是它的工作方式。 所有功能都从 -100 https://blogs.msdn.microsoft.com/ericgu/2004/01/12/minus-100-points/开始。 从那个帖子

在其中一些问题中,这些问题会询问我们为什么“删除”或“删除”某个特定功能。 这个措辞意味着我们从现有的语言开始(C++ 和 Java 是这里的流行选择),然后开始删除功能,直到达到我们喜欢的程度。 而且,尽管有些人可能很难相信,但这不是语言的设计方式。

我们有一个有限的复杂性预算。 我们所做的每一件事都会让我们接下来做的每一件事都慢 0.1%。 当您请求一个功能时,您要求所有其他功能变得更难、更慢、更不可能。 这里没有任何内容,因为 Java 拥有它,或者因为 C# 拥有它,或者因为它只有几行代码,或者你可以添加一个命令行开关。 功能需要为它们自己以及它们对未来的全部负担付出代价。

什么会改变我们的想法? 人们展示的代码示例由于缺少 final 关键字而无法按应有的方式工作。

这里关键字试图解决的情况是有人错误地继承了一个类。 你如何错误地从一个类继承? 这不是一个容易犯的错误。 JavaScript 类本质上足够脆弱,以至于任何子类都必须了解其基类的行为要求,这确实意味着编写某种形式的文档,并且该文档的第一行可以简单地是“不要子类化此类”。 或者可以进行运行时检查。 或者类构造函数可以隐藏在工厂方法后面。 或任何其他数量的选项。

换句话说:在 JavaScript 中从类继承的唯一正确过程总是从阅读该类的文档开始。 基类可能需要简单地派生并开始编码的约束太多。 如果在第一步,您应该已经知道不要尝试,那么在第三步中静态强制提供什么价值?

当情况已经是你应该遇到至少两个单独的障碍告诉你不要在那里开始时,为什么 TypeScript 必须有这个关键字?

我已经编程了大约多年了,我从来没有尝试过对某些东西进行子类化,却发现它是密封的,我不应该尝试。 不是因为我是超级天才! 这只是一个极不常见的错误,在 JavaScript 中,这些情况已经是您首先需要询问基类问题的情况。 那么正在解决什么问题呢?

如果你对加强 Typescript 的主要功能不感兴趣,请大声说出来,以便科技新闻可以在 Twitter 上转发

我们非常明确地说明了我们如何设计语言https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals ; 非目标一号正好说明了这一建议。

阅读这里的许多评论后,我个人的反应是,由于其他语言中存在的结构,存在强烈的偏见效应。 如果你只是从一个空白的角度来看这个,你可以想象出几十种行为,其中 TypeScript 只有一小部分。

你可以很容易地想象一些关键字被应用于方法并强制通过超级https://github.com/Microsoft/TypeScript/issues/21388调用它的派生方法。 如果 C# 和 Java 有这个关键字,人们绝对会在有意义的地方应用这个关键字。 事实上,它可以说比 final 更常见,因为它不可能应用运行时检查,并且是基派生契约的一个比“派生类可能不存在”更微妙的方面。 它在各种方面都有用,而 final 不会。 我更愿意拥有那个关键字而不是这个关键字(尽管我认为两者都不符合复杂性与价值栏)。

那么,考虑实施这一点的适当理由是什么?

当我们查看反馈时,强烈的考虑是这样的:

我无法充分表达这个 JavaScript 库的行为
我的代码编译了,但真的不应该,因为它有这个(微妙的)错误
类型系统未充分传达此代码的意图
final 命中这些,但是一个修饰符也会说“这个函数不能连续调用两次”或“这个类的构造直到下一个异步滴答声才真正完成”。 并非所有可以想象的东西都应该存在。

我们从未见过这样的反馈:“我花了几个小时调试,因为我试图从一个实际上不能子类化的类继承”——有人说这会正确地触发 'wat',因为它不是真的可以想象的。 即使存在修饰符,它也无济于事,因为库不会记录类是否是最终的 - 例如 fs.FSwatcher https://nodejs.org/api/fs.html#fs_class_fs_fswatcher final ? 似乎连节点作者都不知道。 所以如果作者知道它是 final 就足够了,但无论如何都会记录下来,并且缺少 final 并不能真正告诉你任何事情,因为它通常根本不为人所知。


你收到这个是因为你被提到了。
直接回复本邮件,在 GitHub 上查看https://github.com/Microsoft/TypeScript/issues/8306#issuecomment-458268725 ,或将线程设为静音https://github.com/notifications/unsubscribe-auth/AA3NA4o9wu54CcJyK1C8WHVjXIQwfmsUks5vH1M .

是的,现在讨论很分散。 我希望在您可以投票的跟踪器中看到这些问题,例如用于 IntelliJ/webstorm 问题的问题。 获取有关类和/或方法有多少人希望看到final实际数字。

final 会很有帮助

抽象关键字用于提醒程序员他们应该覆盖该字段。 提醒他们不要覆盖它的关键字将以类似的方式提供帮助。

最终方法对于使代码更加结构化和/或安全性至关重要。

例如,假设您有一个支持 3rd 方插件的应用程序。 您可以强制所有插件扩展一个抽象类,该类为它们必须创建的任何方法实现一个接口,并包含一些控制操作顺序的最终方法,并允许您在操作之间实现挂钩。 因此,您的应用程序不需要知道任何单个插件的细节,并且仍然需要知道它的一些内部状态。

如果没有 final 方法,它们可能会干扰您控制操作顺序的函数; 可能会导致应用程序中出现错误、漏洞或其他意外行为。 虽然大多数情况都有某种变通方法,但这些变通方法可能很复杂、重复,有时甚至不可靠; 而 final 方法在定义方法时使其成为一种简单的解决方案。

此外,虽然此示例特定于插件,但它也适用于其他情况。 (团队中的新开发人员,确保某些逻辑不会超出范围,标准化代码的运行方式,确保基于安全的方法不会改变等。所有这些都可以从中受益)

@RyanCavanaugh虽然有一个很好的例子,render() 想要 final,但没有人提到开放-封闭原则(现在作为 SOLID 的一部分被缩写为第二位)。
对于编写其他人将使用的框架,这将很有用。
如果不是,那么在 Typescript 中采用开闭方法的首选方法是什么?

我会喜欢final的方法(我想一般是属性和声明)。 这造成了真正的麻烦——人们不断地不小心覆盖了东西,却没有意识到下面有一个东西……破坏东西……

我还认为 final方法会很有价值,正如其他人指出的那样,所有反对的论点似乎都集中在 final 类上。 有一个单独的问题 (#9264) 但它被关闭了,因为显然这个问题涵盖了它,但我真的不认为它确实如此。 我们可以重新打开那个问题,或者在这里正确解决它吗?

我的情况类似于@0815fox ,我有一个方法的“内部”版本,它是抽象的,但我希望该方法的“原始”版本永远不会被覆盖,因为它包含重要的行为,我不想要一个子类来覆盖那个。

我感到莫名其妙的是,无论社区压倒性地支持这个功能,它仍然被 TypeScript 开发人员拒绝......

也许一个制衡点是向 TS-Doc 添加一个标准,建议某人不要继承特定的类或覆盖特定的方法。 像@final注释或类似的东西。

好吧,我应该在其他所有投票“最终”的人所说的话中添加什么...这是我的投票。

同意。 当然,如果您将readonly与方法一起使用,您可以将它视为函数类型的属性,但是这种解决方法可能会非常令人困惑。 为这个问题提供本机解决方案会很好。

我认为final ,尤其是final method在类型检查中很重要,因为它经常控制方法的工作流程,保证模板模式或其他设计模式中调用方法的顺序。

final应该是要走的路

Gang - 由于此票证 (a) 已关闭,并且 (b) 对“最终方法”讨论的标题具有误导性,因此我决定创建一个仅关注最终方法的新票证。 希望这将更难以忽视,甚至可能会刺激一些进展。 哦,是的,请参阅https://github.com/microsoft/TypeScript/issues/33446

@瑞安卡瓦诺

复杂性与价值栏

您能否详细说明final复杂之处? 在我看来,这将是最容易实现的关键字之一,特别是考虑到诸如abstract类的关键字已经实现,并且可以从中重用许多逻辑/代码。

@vinayluzrao

作为一个随机示例,目前我们说您可以从具有构造签名的任何内容继承:

// OK
declare const SomeParent: new() => { a: string };
class A extends SomeParent { }

显然final类是new -able:

function make<T>(ctor: new() => T) {
  return ctor;
}
final class Done {
}
// No problem
const d = make(Done); // d: Done

但是现在有一个矛盾:我们有一个可以new东西,但是放入extends子句是不合法的。 所以一级间接击败final

function subvertFinal(ctor: new() => any) {
  class Mwhahaah extends ctor {
    constructor() {
      super();
      console.log("I extended a final class! So much for 'types'!")
    }
  }
}
final class Done {
}
subvertFinal(Done);

这已经让人们在abstract上绊倒了,人们一直在争论abstract类应该有一些隐式构造签名,该签名存在用于类型检查,但实际上并不是可调用的或其他东西。

仅供参考,当我们在这些讨论中说“复杂性”时,90% 的情况下,我们指的是 TypeScript 用户体验到的概念复杂性,而不是我们作为产品开发人员的实现复杂性。 后者是可以处理的(例如,从实现的角度来看,条件类型和映射类型非常复杂); 概念的复杂性不是你可以通过在产品上投入更多和更聪明的工程师来处理的。 每次我们在语言中添加一些导致意外或难以解释的行为的东西时,都会给语言的每个用户带来心理负担。

我只需要 final 方法,因为在相对定期的基础上,有人覆盖(不知道)下面的方法,因此我的代码无法运行,并且事情以非常奇怪的方式中断,人们不知道为什么,并且调试通常需要很长时间。

@RyanCavanaugh真的澄清了,谢谢。 不过,我想抛出一个想法:在我看来,您给出的示例中的问题是由于newextends之间的强耦合而出现的,也许这是更大的问题,而不是final本身。 我想知道为什么要做出这个耦合决定,以及在这一点上撤销它(根本不意味着它应该撤销)会有多困难。

不过,我不确定这是否太切题而不能在这里进行讨论。

这是我遇到的一个问题。 这么简单的代码示例是无法编译的。
final关键字将完美解决它:

abstract class Parent {
    public abstract method(): this;
}

class Child extends Parent {
    public method(): this {
        return new Child();
    }
}

“Child”类型不能分配给“this”类型。
“Child”可分配给“this”类型的约束,但“this”可以使用约束“Child”的不同子类型进行实例化。

操场

请问这个可以重开吗? 很明显需要这个功能

我们在各种会议上讨论了十几次这个问题,但每次都反对。 第十三次重新打开这个讨论不是很好地利用我们的时间。

你可以不同意 TypeScript 团队的决定,这很好,但我们不能整天绕着圈子重新重新重新重新重新重新重新决定人们不同意的决定。 在这个 repo 再次提出之前,这个 repo 中还有一千个其他建议值得初步考虑。

@nicky1038的评论这样的建设性讨论在这里非常受欢迎; 我会要求人们不要为了新闻/重新考虑请求而不断地 ping 这个线程,这样我们就不必锁定它。

我在这里也支持为方法添加 final。 我有一个带有名为 _init_ 的函数的基类,我不希望子类能够覆盖它。 最终将是完美的解决方案。

我还支持为方法添加final关键字。
如今,它已成为面向对象编程的标准。

添加final对我来说很有意义。 特别是在构建供他人使用的库/框架时,您甚至不想冒覆盖函数的风险。

如果我没有记错的话,在使用泛型时添加final可以帮助避免'myObj' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'MyClass'错误的麻烦,因为我可以制作MyClass final并因此保证那里不是任何其他子类型。
不是 100% 确定,但我认为这就是您可以在 Java 中解决此问题的方法。

我们有同样的东西,我们正在制作一个有角度的基础组件,许多其他包可以或需要扩展为基础组件。
因为我们有诸如 ngOnChanges 甚至 ngOnInit 之类的东西,只有基类应该实现,子类不应该覆盖这些,但真正实现了 ngOnInitReal(或我们称之为什么),所以我们完全控制何时被调用和什么。
因为我们无法控制这一点,所以我们只能通过文档来做到这一点。 如果一个组件制造商不读这个,它的组件可能会损坏,或者它需要从我们的基础上复制大量代码来做同样的事情。

因此,如果没有最终方法,我们就无法真正制定坚如磐石的 api 合约。

有没有人提到

class Fancy {
    readonly secureFoo = (message: string) => {
        console.log(message);
    }
}

有没有人提到

class Fancy {
    readonly secureFoo = (message: string) => {
        console.log(message);
    }
}

虽然您可以这样做,但还是存在一些差异。 例如,类的每个实例都有自己的函数副本,而不是只有原型具有每个人都使用的函数。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

yortus picture yortus  ·  157评论

xealot picture xealot  ·  150评论

OliverJAsh picture OliverJAsh  ·  242评论

Gaelan picture Gaelan  ·  231评论

fdecampredon picture fdecampredon  ·  358评论