为什么受保护的成员可以在 TypeScript 中被公共成员覆盖?



我是 Typescript 的新手,我试着在这个操场上玩了一下 TypeScript。我注意到在 TypeScript 中,基类中的受保护成员可以被公共成员覆盖:

class Base {
protected name: string = '!'
}
class Derived extends Base{
public name: string = '?'
}

一方面,这对我来说是有意义的,因为 Liskov 替换原则仍然成立:基类比派生类有更严格的要求。但另一方面,我注意到私人成员不能被受保护或公共成员覆盖,这对我来说似乎不一致:

class Base {
private name: string = '!'
}
class Derived extends Base{
public name: string = '?'  // ERROR!
}

因此,我想知道:

  1. 我的观察是预期行为还是 Typescript 中的错误?

  2. 如果是有意的,为什么存在这种不一致?为什么 TypeScript 不要求所有重写成员与基类中的成员具有相同的可访问性?还是允许所有具有更高可访问性的派生成员覆盖基类中的成员?

这是预期的行为。

可以将protected字段设置为publicprotected因为它允许派生类读取和写入字段。派生类可以选择使用其读取和写入字段的能力,以允许其他人读取和写入字段。没有必要让你写这样的东西:

class Foo {
protected someField;
}
class Bar extends Foo {
public get someFieldButPublic() {
return this.someField;
}
public set someFieldButPublic(value) {
this.someField = value;
}
}

如果你只想公开someField

您无法将private字段设为protectedpublic,因为您对该字段没有读取或写入访问权限。这是private;毕竟,如果基类希望您访问该字段,他们就会将其设为protected

这是预期行为。

TypeScript 编译为 JavaScript。因此,在代码中使用访问修饰符对输出绝对没有影响。访问修饰符唯一要做的就是让编译器在你使用不应该访问的东西时对你大喊大叫。

示例:这两个类编译为完全相同的代码。操场(忽略类名)

// prop is private
class Test {
private prop: string;
constructor() {
this.prop = "str"
}
}
// prop is public
class Test {
public prop: string;
constructor() {
this.prop = "str"
}
}

在 C# 等语言中,私有属性是真正的私有属性,因此可以在公开 name 属性的同时从具有私有名称属性的类继承。访问this.name的继承方法将访问基类中的name属性,而类中访问this.name的方法将使用继承类中的属性。

让我们看一下你的示例发出的 JavaScript。

var __extends; // omitted for brevity
var Base = (function () {
function Base() {
this.name = '!';
}
return Base;
}());
var Derived = (function (_super) {
__extends(Derived, _super);
function Derived() {
var _this = _super.apply(this, arguments) || this;
_this.name = '?';
return _this;
}
return Derived;
}(Base));

如您所见,这里发生的事情是Base类将!分配给this.nameDerived然后将Base的假定私有name属性更改为?。显然,当Base类中的方法引用this.name并获取Derived类分配的意外值时,这可能会导致一些令人难以置信的混乱错误。

JavaScript 没有这个能力,因为在 Java 中称为字段重写。让我们看看 Java 中的代码片段:

class Base {
protected String name = "!";
public String bar() {
return name;
}
}
class Derived extends Base {
public String name = "?";
public String foo() {
return name;
}
}

当你在java中持有一种派生实例it时,it.foo()返回"?",it.bar()返回"!".但是为什么这两种方法都返回"?"在 JavaScript 中,由于 JavaScript 行为发生在运行时并将this类型绑定到 Base 中的 Derived 中,所以你不能在 javascript 中重写字段。你还记得Function.prototype.call(thisArg)吗?在执行类似操作的派生类中,如果您在派生实例上调用 bar 方法,它实际上将派生类型绑定到 Base 类。 您可以禁止这样做,即在 Base 中将字段设为私有,并让编译器仅在打字稿中告诉您此错误。

最新更新