根据我的理解,这就是在类中定义接口的方式:
class A {
a: number
constructor() {
this.a = 0
}
}
这就是在类外定义接口的方式:
interface A {
a: number
}
class A {
constructor() {
this.a = 0
}
}
这两种情况有什么区别?我什么时候应该使用其中一种而不是另一种?我注意到
private
和 protected
修饰符不能在界面中使用,所以如果我需要使用它们,那么我当然需要使用内部案例。但如果我只使用public
,那么它们之间似乎没有什么区别?
阅读Classes和Everyday Types的文档没有帮助。
您所说的“在类中定义接口”只是编写类的正常方式。 Fields一般在类中声明:
class A {
a: number
constructor() {
this.a = 0
}
}
如果您编译上述内容以针对现代 JavaScript 并使用适当的编译器选项,它将生成一个带有 public class 字段:
的 JavaScript 类// JavaScript
class A {
a; // <-- public class field declaration
constructor() {
this.a = 0;
}
}
另一方面,您所说的“在类外部定义接口”被称为声明合并。接口与类实例类型合并,这在类型级别对其进行了“修补”。接口声明绝对没有运行时效果,因此通常放入类声明中的任何内容都可能表现不同。例如:
interface B {
a: number
}
class B {
constructor() {
this.a = 0
}
}
如果使用与上面相同的编译器设置进行编译,您将获得以下 JavaScript,没有类字段声明:
// JavaScript
class B {
// <-- no public class field declaration
constructor() {
this.a = 0;
}
}
这两种方法可能会产生不同的 JavaScript,这一事实表明它们并不相同,尽管在此示例中几乎没有实际差异。不过,您可以轻松地更改存在明显差异的内容:
class C { a?: number }
console.log(Object.keys(new C()).length) // 1
interface D { a?: number }
class D { }
console.log(Object.keys(new D()).length) // 0
当然存在类型检查差异。接口合并添加内容而无需通过正常类型的类型检查:
class E { a: number } // compiler error
// ~
// Property 'a' has no initializer and is not definitely
// assigned in the constructor.
new E().a.toFixed(); // runtime error
interface F { a: number }
class F { } // no compiler error
new F().a.toFixed(); // runtime error
在上面,
E
给你一个编译器错误,你忘记初始化a
,而F
不会给你这个错误,即使存在同样的问题。因此 E
会警告您 F
没有注意到的问题。
此外,接口不是类声明,因此许多通常可以执行的
class
特定操作在接口合并中不可用。 最明显的例子是类允许您编写运行时代码,而接口则不允许。您可以初始化类字段并实现类方法,但是“初始化”接口属性或“实现”接口方法没有多大意义:
class G { a: number = 1; b(): number { return 2 } };
interface H { a: number = 1, b(): number { return 2 }} // errors!
private
或 protected
或 static
或 abstract
在界面中不可用:
abstract class I {
abstract a: number;
private b?: number;
static c: number;
}
interface J {
abstract a: number; // error!
private b?: number; // error!
static c: number; // error!
}
至于你应该做什么,这至少有些主观。在我看来,声明合并是一种高级技术,仅用于解决现有代码中的限制。如果你可以在不使用它的情况下做某事,你应该这样做。