文章目录
TS 采用的是结构化类型系统,类型检查关注的是值的类型所具有的形状。在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。
子类型:子类型继承了父类型所有的特性,并添加了父类型没有的特性。
对象的类型兼容性
下面实例中,p1 与 p2 具有相同的类型结构,因此二者之间可以相互赋值(在标明类型系统中(C#、Java 等)类型无法兼容);Point3D 是子类型,具有父类型(Point)的所有特性(x , y),且拥有父类型所不具有的特性(z)。因此子类型(Point3D)变量 p3 可以赋值给父类型(Point)变量 p1,反过来则不可以。
// 定义对象
type Point = { x: number, y: number };
type Point2D = { x: number, y: number };
type Point3D = { x: number, y: number, z: number };
// 两个对象的类型具有相同的形状
let p1: Point = { x: 1, y: 1 };
let p2: Point2D = { x: 2, y: 2 };
p1 = p2;
p2 = p1;
// 两个对象的类型形状不相同--子类型可以传给父类型
let p3: Point3D = { x: 3, y: 3, z: 3 };
p1 = p3;
p3 = p1; // 父类型赋值给子类型 --报错
类的类型兼容性
类也可以理解为是对对象的约束,因此类之间的类型兼容性,类似于对象。并且,class 和 type 之间也可以兼容。
// 定义类
class Point {
x!: number
y!: number
}
class Point2D {
x!: number
y!: number
}
class Point3D {
x!: number
y!: number
z!: number
}
// 两个类的类型形状相同
let p1: Point = new Point2D();
let p2: Point2D = new Point();
// 子类型可以赋值给父类型
let p3: Point = new Point3D();
let p4: Point3D = new Point(); // 父类型赋值给子类型 --报错
class 与 type 之间的兼容
// 定义类
class Point2D {
x: number = 2;
y: number = 2;
}
class Point3D {
x: number = 3;
y: number = 3;
z: number = 3;
}
// 定义对象类型
type tPoint = { x: number, y: number };
let t0: tPoint = { x: 0, y: 0 };
// 类型形状相同
let t1: tPoint = new Point2D();
let t2: Point2D = t0;
// 子类型可以赋值给父类型
let t3: tPoint = new Point3D();
let t4: Point3D = t0; // 父类型赋值给子类型 --报错
接口的类型兼容性
接口也可以表示为对对象的约束, 因此接口之间的类型兼容性,也类似于对象,类似于类。并且,interface 和 class 和 type 之间也可以兼容。
// 定义接口
interface Point {
x: number
y: number
}
interface Point2D {
x: number
y: number
}
interface Point3D {
x: number
y: number
z: number
}
// 两个变量类型具有相同的形状
let p1: Point = { x: 1, y: 1 };
let p2: Point2D = { x: 2, y: 2 };
p1 = p2;
p2 = p1;
// 子类型可以传给父类型
let p3: Point3D = { x: 3, y: 3, z: 3 };
p1 = p3;
p3 = p1; // 父类型赋值给子类型 --报错
interface 和 class 和 type 之间的兼容
// 定义类型
type Point = { x: number, y: number };
// 定义类
class Point2D {
x!: number
y!: number
}
// 定义接口
interface Point3D {
x: number
y: number
z: number
}
let p1: Point = { x: 0, y: 0 };
let p2: Point2D = new Point2D();
let p3: Point3D = { x: 0, y: 0, z: 0 };
// 两个变量类型具有相同的形状
p2 = p1;
p1 = p2;
// 子类型可以传给父类型
p2 = p3;
p3 = p2; // 父类型赋值给子类型 --报错
函数的类型兼容性
函数之间兼容性需要考虑三个因素:1 参数个数 2 参数类型 3 返回值类型
【函数参数个数】
排除参数类型和返回值类型对兼容性的影响,参数多的兼容参数少的(参数少的可以赋值给参数多的)
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
// 参数少的可以赋值给参数多的
let f2: F2 = (a: number) => { };
let f1: F1 = (a: number, b: number) => { }; // --报错
数组的 forEach 方法就是使用的这个特性
【函数参数类型】
相同位置的参数类型要相同(原始类型)或兼容(对象类型)
下面示例中,函数类型 F2 兼容函数类型 F1,因为 F1 和 F2 的第一个参数类型相同。
type F1 = (a: number) => void
type F2 = (a: number) => void
// 相同位置参数类型相同
let f1: F1 = (a: number) => { };
let f2: F2 = f1;
如果参数是对象类型的,可以将对象拆开,每一个属性视为一个参数。(参数多的兼容参数少的)
// 对象类型
type Point2D = {
x: number
y: number
}
type Point3D = {
x: number
y: number
z: number
}
type F3 = (p: Point2D) => void // 相当于有 2 个参数
type F4 = (p: Point3D) => void // 相当于有 3 个参数
let f3: F3 = ({ x, y }) => { }
let f4: F4 = f3; // 参数少的赋值给参数多的
【函数返回值类型】
函数的兼容性从返回值考虑,只关注返回值类型本身即可。如果返回值类型是原始类型,此时两个类型要相同;如果返回值类型是对象类型,此时子类型可以赋值给父类型。
// 原始类型:
type F5 = () => string
type F6 = () => string
let f5: F5 = () => { return "a" }
let f6: F6 = f5;
// 对象类型:
type F7 = () => { name: string }
type F8 = () => { name: string; age: number }
// 子类型赋给父类型
let f7: F7 = () => { return { name: "a", age: 0 } };
let f8: F8 = () => { return { name: "a" } }; // 父类型赋给子类型 --报错