1. 数据类型
1.1 字符串类型 string
let name: string = 'tangtangtang'
1.2 数字类型 number
let age: number = 20
1.3 布尔类型 boolean
let married: boolean = false
1.4 数组类型 array
数组有以下两种写法
let arr1: number[] = [1,2,3]
let arr2: Array<number> = [4,5,6]
1.5 元组类型 tuple
元组表示一个已知数量和类型的数组。不能多写也不能少写。
let name: [string,number,boolean] = ["tangtangtang",20,false]
元组和数组的比较
| 元组 | 数组 |
|---|---|
| 每一项可以是不同的类型 | 每一项都是相同的类型 |
| 有预定义的长度 | 没有长度限制 |
| 用于表示一个结构 | 用于表示一个列表 |
1.6 枚举 enum
- 事先考虑某一个变量的所有可能的值,它的值一般是固定的。
- 比如说性别、年龄、月份、星期、颜色、学历。
- 枚举又分为普通枚举和常数枚举。
1.6.1 普通枚举
enmu Gender {
GIRL,
BOY
}
console.log(Gender.GIRL,Gender["GIRL"],Gender[0]) // 0 0 GIRL
console.log(Gender.BOY,Gender["BOY"],Gender[1]) // 1 1 BOY
console.log(`糖糖糖是${Gender[0]}`) // 糖糖糖是GIRL
1.6.2 常数枚举
- 常数枚举和普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。
- 假如包含了计算成员,则会在编译阶段报错。
const enum Colors {
Red,
Yellow,
Blue
}
let myColor = [Colors.Red,Colors.Yellow,Colors.Blue]
1.7 任意类型 any
any可以赋值给任意类型。
如果被定义为any类型,则不进行类型检查。
let ele: any = document.getElementById("box")
ele.style.background = "blue"
扩展: !表示非空断言
let ele2: (HTMLElement | null) = document.getElementById('box2')
ele2!.style.color = 'green';
1.8 null 和 undefined
null和undefined是其他类型的子类型,可以赋值给其他类型。比方说数字类型,字符串类型,赋值后的类型会变成null或undefined。- 如果
strictNullChecks的值为true,则不能把nullundfined赋值给变量 (通常建议把这个打开,让代码更加健壮)。
let a: number;
a = 1;
a = undefined;
a = null;
let z: undefined = undefined;
let z2: any = undefined;
let z3: null = null;
let z4: any = null;
y或者是number类型或者是null类型或者是undefined类型
let y: number | null | undefined;
y = 1;
y = undefined;
y = null ;
1.9 never 类型
never 代表不会出现的值
作为不会返回的函数的返回值类型。 参数是string,返回never类型
function error(message: string): never {
throw new Error(message); // 直接异常结束
}
function loop(): never { // 死循环
while(true) {
}
console.log(`ok`)
}
1.10 void 类型
function greeting(): void {
return undefined
}
void类型
- void 表示没有任何类型。 当一个函数没有返回值时,TS会认为它的返回值是
void类型。 - 当我们声明一个变量类型是void的时候,它的非严格模式下仅可以被赋值为
null和undefined。 - 当
strictNullChecks = true,null就不能赋值给void。 never类型 never是其他类型(null undefined)的子类型,代表不会出现的值。- 拥有
void返回值类型的函数能正常运行,拥有never返回值类型的函数无法正常运行,无法终止或会抛出异常。
1.11 Symbol
代表唯一不变的值
const s1 = Symbol("key")
const s2 = Symbol("key")
console.log(s1 === s2) // 报错
1.12 BigInt 大数字
可以用它存储超大数字,使用的时候必须要加ESNext。
const max = BigInt(Number.MAX_SAFE_INTEGER);
console.log(max+BigInt(1) === max+BigInt(2))
console.log(max+1n === max+2n) // "target": "ESNext", //要改成ESNext
- number和bigint两个不兼容,注意这两个都是小写
- Number 和 Bigint 是JS类型,number 和 bigint 是ts类型
let foo: number;
let bar: bigint;
foo = bar;// 报错
bar = foo;// 报错
1.13 类型推论
- 是指编程语言中能够自动推导出值的类型的能力,它是一些强静态类型语言中出现的特性。
- 定义时未赋值就会推论成
any类型。 - 如果定义的时候就赋值就能利用到类型推论。
let name;
name = 1; // name的类型为any
let uname = "string"
uname = 1; // 因为已经定义为string 类型,这里就不能再赋值为number类型了
1.14 包装对象 Wrapper Object
原始类型 和对象类型
let name2 = "tangtangtang"
// 如果内部自动帮你包装成对象类型
console.log(name2.toUpperCase()); //new String(name2).toUpperCase(); //TANGTANGTANG
console.log((new String('tangtangtang')).toUpperCase()); //TANGTANGTANG
当调用基本数据类型方法的时候,JavaScript会在原始数据类型和对象类型之间做一个迅速的强制性切换。
let isOk1: boolean = true;
let isOk2: boolean = Boolean(1);
// let isOk3: boolean = new Boolean(1); //报错,不能把对象类型赋值给基本
类型
1.15 联合类型
- 取值可以是多种类型中的某一种。
- 没赋值只能访问公共的属性方法,赋值后可以访问私有的 ,联合类型上只能访问两个类型共有的属性和方法。
let name3: string | number;
console.log(name3!.toString()); // 报错
name3 = 3;
console.log(name3.toFixed(2)); // 3.00
name3 = "tangtangtang"
console.log(name3!.length); // 12
1.16 类型断言
- 类型断言可以将一个联合类型的变量,指定为一个更加具体的类型。
- 不能将联合类型断言为不存在的类型。
- 类型断言有两种形式:
// as语法
let name4: string | number;
console.log((name4! as number).toFixed(2))
console.log((name4! as string).length);
// 用两个as 做双重断言
console.log(name4! as any as boolean);
//尖括号语法
let value: any = 'this is a string';
let strLength: number = (<string>value).length;
在工作中我们更推荐使用as语法。 因为尖括号格式会与react中JSX产生语法冲突。
1.17 字面量类型和类型字面量
字面量类型
const up: 'Up' = 'Up'; // 变量up ,类型叫Up(字面量类型),值是Up
const down: 'Down' = 'Down';
const left: 'Left' = 'Left';
const right: 'Right' = 'Right';
type Direction = 'Up' | 'Down' | 'Left' | 'Right'; // type类型别名声明一个类型Direction
//可以实现枚举的效果
function move(direction: Direction) {
}
move('Down');
类型字面量
type Person = {
name: string,
age: number
}
let p1: Person = {
name: "tangtangtang",
age:10
}
字符串字面量和联合类型的区别
- 字符串字面量类型用来约束取值只能是某几个字符串中的一个, 联合类型
Union Types表示取值可以为多种类型中的一种。 - 字符串字面量限定了使用该字面量的地方仅接受特定的值,联合类型 对于值并没有限定,仅仅限定值的类型需要保持一致。
type T1 = '1' | '2' | '3';
type T2 = string | number | boolean;
let t1: T1 = '1';
let t2: T2 = true;
let name = 'tangtangtang';
console.log(name.toUpperCase()); //TANGTANGTANG
console.log((new String('tangtangtang')).toUpperCase()); //TANGTANGTANG
1.18 Unknown 类型
unknown 和 any 的主要区别是 unknown 类型会更加严格 在对 unknown 类型的值执行大多数操作之前 我们必须进行某种形式的检查 而在对 any 类型的值执行操作之前 我们不必进行任何检查 所有类型都可以被归为 unknown 但unknown类型只能被赋值给 any 类型和 unknown 类型本身 而 any 什么都能分配和被分配
let value: unknown;
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
1.19 类型别名
类型别名用来给一个类型起个新名字。
type flag = string | number;
function hello(value: flag) {}
1.20 交叉类型
交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
type Flag1 = { x: number };
type Flag2 = Flag1 & { y: string };
let flag3: Flag2 = {
x: 1,
y: "tangtangtang"
};
1.21 类型保护
类型保护就是一些表达式,他们在编译的时候就能通过类型信息确保某个作用域内变量的类型 其主要思想是尝试检测属性、方法或原型,以确定如何处理值。
typeof 类型保护
function double(input: string | number | boolean) {
if (typeof input === "string") {
return input + input;
} else {
if (typeof input === "number") {
return input * 2;
} else {
return !input;
}
}
}
in 关键字
interface Bird {
fly: number;
}
interface Dog {
leg: number;
}
function getNumber(value: Bird | Dog) {
if ("fly" in value) {
return value.fly;
}
return value.leg;
}
instanceof 类型保护
class Animal {
name!: string;
}
class Bird extends Animal {
fly!: number;
}
function getName(animal: Animal) {
if (animal instanceof Bird) {
console.log(animal.fly);
} else {
console.log(animal.name);
}
}
自定义类型保护
通过 type is xxx这样的类型谓词来进行类型保护。
例如下面的例子 value is object就会认为如果函数返回 true 那么定义的 value 就是 object 类型。
function isObject(value: unknown): value is object {
return typeof value === "object" && value !== null;
}
function fn(x: string | object) {
if (isObject(x)) {
// ....
} else {
// .....
}
}
2. 函数
2.1 函数的定义
给函数指定参数类型和返回值类型。
参数name的类型是string,返回值类型是void,void约束返回值。
function hello(name: string): void {
console.log('hello',name); //hello tangtangtang
}
hello('tangtangtang');
2.2 函数表达式
type GetName 是定义函数类型, GetName是类型。
type GetName = (firstName: string, lastName: string) => string
// 定义类型: let 变量:类型 = 函数表达式, 返回firstName + lastName
let getName: GetName = function (firstName: string, lastName: string): string {
return firstName + lastName;
}
2.3 没有返回值
函数没有返回值就用void。
let hello = function(name: string): void {
console.log('hello',name) // hello sweettangtangtang
}
hello('sweettangtangtang')
2.4 可选参数
在TS中函数的形参和实参必须一样,不一样就要配置可选参数,而且必须是最后一个参数。
eg: 可选参数 age?:number 意思就是age可以传可以不传。
function handle(name: string, age?: number): void {
console.log(name,age); // tangtangtang 666
}
handle('tangtangtang',666);
2.5 默认参数
参数method类型string,默认值GET
function ajax(url: string, method: string = "GET") {
console.log(url,method) // /getuser GET
}
ajax('/getuser');
2.6 剩余参数
参数 nunmbers类型是number数组。
function sum(...nunmbers: number[]) {
return nunmbers.reduce((val,item) => val+item,0)
}
console.log(sum(1, 2, 3, 4, 5, 6)) // 21
2.7 函数重载
在TS中,函数重载指给同一个函数提供多个函数类型定义。
同样一个参数,它传的方式可以不一样。
eg: 如果传的val是个字符串,赋值给obj.name,如果传的val是个数字,赋值给obj.age。
let obj: any = {};
function overloading(val: string): void;
function overloading(val: number): void;
function overloading(val: any): void {
if(typeof val === 'string') {
obj.name = val;
console.log(obj.name); //tangtangtang
} else if(typeof val === 'number') {
obj.age = val;
console.log(obj.age); //20
}
}
overloading("tangtangtang");
overloading(20);
以上三个函数必须要写在一起,不能分开。
Warning:函数重载真正执行的是同名函数最后定义的函数体, 在最后一个函数体定义之前全都属于函数类型定义 ,不能写具体的函数实现方法,只能定义类型。
3. 类
3.1 类的定义
通过class关键字定义类
class Person {
name: string;
getName(): void {
console.log(this.name);//tangtangtang
}
}
let p1 = new Person();
p1.name = "tangtangtang";
p1.getName();
3.2 存取器
我们可以通过存取器来改变一个类中属性的读取和赋值行为。
class User {
myname: string;
constructor(myname: string) { //constructor构造函数
this.myname = myname;
}
// get set 叫属性存取器
get name() { // 读取
return this.myname;
}
set name(value) { //存取
this.myname = value;
}
}
let user = new User('hahaha');
user.name = 'world';
console.log(user.name); //world
将上面的代码转义成 es5 的代码:可以看出原理很简单,使用了 Object.defineProperty 在类的原型上面拦截了属性对应的 get 和 set 方法。
var User = /** @class */ (function () {
function User(myname) {
this.myname = myname;
}
Object.defineProperty(User.prototype, "name", {
get: function () {
return this.myname;
},
set: function (value) {
this.myname = value;
},
enumerable: false, // 是否可枚举
configurable: true, // 是否可配置
});
return User;
})();
var user = new User("hahaha");
user.name = "world";
console.log(user.name);
3.3 参数属性
class User {
//myName:string;
constructor(public myname: string) {
//加了public关键那么等价于myName:string; this.myName = myName ,实例上自动拥有了myName属性
//this.myName = myName
}
get name() {
return this.myname;
}
set name(value) {
this.myname = value;
}
}
let user = new User('hahaha');
console.log(user.name);
user.name = 'world';
console.log(user.name);
3.4 readonly只读属性
readonly修饰的变量只能在构造函数中初始化。
TypeScript 的类型系统同样也允许将 interface、type、 class 上的属性标识为 readonly。
readonly 实际上只是在编译阶段进行代码检查。而 const 则会在运行时检查(在支持 const 语法的 JavaScript 运行时环境中)。
class Animal {
public readonly name: string
constructor(name: string) {
this.name = name;
}
changeName(name: string) {
this.name = name; // 报错
}
}
let a = new Animal('dog');
a.changeName('cat');
3.5 继承
子类继承父类后,子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性。
将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑。
super可以调用父类上的方法和属性。
class Person { // Person类 父类
name: string; //定义实例的属性,默认省略public修饰符
age: number;
constructor(name: string, age: number) { //构造函数
this.name = name;
this.age = age;
}
getName(): string {
return this.name;
}
setName(name: string): void {
this.name = name;
}
}
//在 TS 中,我们可以通过 extends 关键字来实现继承。
class Student extends Person { // Student通过extends关键字继承父类Person
stuNo: number;
constructor(name: string, age: number, stuNo: number) {
super(name,age); // super可以调用父类上的方法和属性
this.stuNo = stuNo;
}
getStuNo() {
return this.stuNo;
}
}
let s1 = new Student('tangtangtang',20,1);
console.log(s1); //tangtangtang 20 1
3.6 类里面的修饰符
public 公开的,private 私有的 ,protected 受保护的 。
class Father {
// static fatherName: string = 'fatherName'; //static 静态属性
// toString() {
// console.log('Father');
// }
public name: string;//public: 自己、自己的子类和其它类都能访问
protected age: number;//protected: 自己和自己子类能访问,其它类不能访问
private money: number;//private: 自己能访问,子类和其它类不能访问
constructor(name: string, age: number, money:number) { // 构造函数
this.name = name;
this.age = age;
this.money = money;
}
getName(): string {
return this.name;
}
setName(name:string): void{
this.name = name;
}
}
class Child extends Father {
// static childName: string = 'childName'; // 静态属性
constructor(name: string, age: number, money: number) {
super(name, age,money);
}
// public toString() {
// super.toString(); //调用父类的toString方法。
// console.log('Child');
// }
// public desc() {
// console.log(this.name,this.age);
// }
desc() {
console.log(this.name,this.age,this.money); // money访问不到,会报错
}
}
let child = new Child('tangtangtang',20,10000);
console.log(child.name); // tangtangtang
console.log(child.age); // age访问不到,会报错
console.log(child.money); //money访问不到,会报错
子类的同名属性会覆盖父类的同名属性。
父类调不了子类的方法。
3.7 静态属性和静态方法
类的静态属性和方法是直接定义在类本身上面,所以也只能通过直接调用类的方法和属性来访问。
class Mom {
static className = 'Mom';
static getClassName() {
return Mom.className;
}
public name: string;
constructor(name: string) {//构造函数
this.name = name;
}
}
console.log(Mom.className); //Mom
console.log(Mom.getClassName()); //Mom
3.8 抽象类
抽象描述一种抽象的概念,无法被实例化,只能被继承。
无法创建抽象类的实例。
抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现。
使用 abstract 关键字来定义抽象类和抽象方法 。
abstract class Animal {
name!: string;
abstract speak(): void;
}
class Dog extends Animal {
speak() {
console.log('汪汪汪');
}
}
let animal = new Animal(); //直接报错 无法创建抽象类的实例
let dog = new Dog();
dog.speak();
| 访问控制修饰符 | private protected public |
|---|---|
| 只读属性 | readonly |
| 静态属性 | static |
| 抽象类、抽象方法 | abstract |
3.9 抽象类和接口
-
不同类之间公有的属性或方法,可以抽象成一个接口
Interfaces。 -
而抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现。
-
抽象类本质是一个无法被实例化的类,其中能够实现方法和初始化属性,而接口仅能够用于描述,既不提供方法的实现,也不为属性进行初始化。
-
一个类可以继承一个类或抽象类,但可以实现
implements多个接口。 -
抽象类也可以实现接口。
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
abstract speak(): void;
}
interface Flying {
fly(): void;
}
class Duck extends Animal implements Flying {
speak() {
console.log('汪汪汪');
}
fly() {
console.log('我会飞');
}
}
let duck = new Duck('tangtangtang');
duck.speak();
duck.fly();
3.10 抽象方法
抽象类和方法不包含具体实现,必须在子类中实现。
抽象方法只能出现在抽象类中。
abstract class Animal {
abstract speak(): void;
}
class Dog extends Animal {
speak() {
console.log('小狗汪汪汪');
}
}
class Cat extends Animal {
speak() {
console.log('小猫喵喵喵');
}
}
let dog = new Dog();
let cat = new Cat();
dog.speak();
cat.speak();
3.11 重写override 和 重载overload
重写是指子类重写继承自父类中的方法。
重载是指为同一个函数提供多个类型定义。
class Cat6 extends Animal6 {
speak(word: string): string {
return 'Cat:'+ word;
}
}
let cat6 = new Cat6();
console.log(cat6.speak('hello'));
function double(val: number): number
function double(val: string): string
function double(val: any): any{
if(typeof val == 'number'){
return val *2;
}
return val + val;
}
let r = double(1);
console.log(r);
3.12 继承 和 多态
继承Inheritance子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性。
多态Polymorphism由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。
class Animal7 {
speak(word: string): string{
return 'Animal: '+ word;
}
}
class Cat7 extends Animal7 {
speak(word: string): string{
return 'Cat:'+ word;
}
}
class Dog7 extends Animal7 {
speak(word: string): string{
return 'Dog:'+ word;
}
}
let cat7 = new Cat7();
console.log(cat7.speak('hello')); // Cat:hello
let dog7 = new Dog7();
console.log(dog7.speak('hello')); // Dog:hello
3.13 装饰器
- 类装饰器
@方法放在类的前面就叫做装饰器
通过装饰器@addNameEat
在一个模块里声明两个一模一样的变量,会报错,这个时候我们要用命名空间 namespace 命名空间,名字随便定义 。
namespace a { // a 叫什么都可以 。。
function addNameEat(x: Function) { // x叫什么名字都可以
x.prototype.name = 'tangtangtang';
x.prototype.eat = function () { }
}
@addNameEat
class Person {
name: string;
eat: Function;
constructor() { }
}
let p: Person = new Person();
console.log(p.name); //tangtangtang
p.eat();
}
- 类装饰器工厂 addNameEatFactory
namespace b {
function addNameEatFactory(name: string) {
return function addNameEat(x: Function) {
x.prototype.name = name;
x.prototype.eat = function () { }
}
}
@addNameEatFactory('tangtangtang')
class Person {
name: string;
eat: Function;
constructor() { }
}
let p: Person = new Person();
console.log(p.name); // tangtangtang
p.eat();
}
- 类的装饰器
可以多,但不能少。
namespace c {
function replaceClass(constructor: Function) {
return class { // 返回一个新的类
name: string;
eat: Function;
age: number;
constructor() { }
}
}
@replaceClass
class Person {
name: string;
eat: Function;
constructor() { }
}
let p: Person = new Person();
console.log(p.name);
p.eat();
}
- 属性装饰器
属性装饰器能装饰属性也能装饰方法。
namespace d {
//如果装饰的是实例属性的话,target是构造函数的原型
function upperCase(target: any, propertyKey: string) {
let value = target[propertyKey];
const getter = () => value;
const setter = (newVal: string) => {value = newVal.toUpperCase()}
if (delete target[propertyKey]) { // 删除属性
Object.defineProperty(target,propertyKey,{ // 重新定义属性
get: getter,
set: setter,
enumerable: true,
configurable:true
});
}
}
//如果装饰的是静态属性的话,target是构造函数本身
function staticPropertyDecorator(target: any, propertyKey: string) {
console.log(target, propertyKey); //[Function: Person] { age: 10 } age
}
// 把可枚举的变成不可枚举
function noEnumerable(target: any, propertyKey: string,descriptor:PropertyDescriptor) {
console.log(target); //Person { getName: [Function], sum: [Function], name: [Getter/Setter] }
console.log(propertyKey); //getName
descriptor.enumerable = false;
}
// 求和
function toNumber(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let oldMethod = descriptor.value;
descriptor.value = function (...args: any[]){
args = args.map(item => parseFloat(item));
return oldMethod.apply(this, args);
}
}
class Person {
@upperCase
name:string = 'tang';//实例属性
@staticPropertyDecorator
static age: number=10;//静态属性
@noEnumerable
getName() { console.log(this.name); }//实例方法
@toNumber
sum(...args: any[]){//实例方法
return args.reduce((accu: number, item: number) => accu + item,0);
}
}
let p = new Person();
console.log(p.name); // TANG
console.log(p.sum('1', '2', '3')); //6
}
- 参数装饰器
namespace e{
//target 静态成员就是构造函数 非静态成员就是构造函数原型 方法的名称 参数的索引
function addAge(target: any,methodName: any,paramIndex: number){
console.log(target, methodName, paramIndex);
target.age = 10;
}
class Person{
age:number;
login(username: string,@addAge password: string){
console.log(this.age,username,password);
}
}
let p = new Person();
p.login('1','2');
}
- 装饰器的执行顺序
namespace f {
function ClassDecorator1() {
return function(target: any) {
console.log('ClassDecorator1');
}
}
function ClassDecorator2() {
return function (target: any) {
console.log('ClassDecorator2');
}
}
function PropertyDecorator(name: string) {
return function (target: any,propertyName: any) {
console.log('PropertyDecorator', propertyName, name);
}
}
function MethodDecorator() {
return function (target: any, propertyName: any) {
console.log('MethodDecorator', propertyName);
}
}
function ParameterDecorator() { // @ParameterDecorator() 参数装饰器
return function (target: any, methodName: any,index: any) {
console.log('ParameterDecorator', methodName, index);
}
}
@ClassDecorator1() // 类装饰器
@ClassDecorator2() // 类装饰器
class Person {
@PropertyDecorator('name') //属性装饰器工厂
name:string = '';
@PropertyDecorator('age')
age:number = 10;
@MethodDecorator() // 方法装饰器
hello(@ParameterDecorator() p1:string, @ParameterDecorator() p2:string){}
}
}
装饰器的执行顺序如下:
PropertyDecorator name name
PropertyDecorator age age
ParameterDecorator hello 1
ParameterDecorator hello 0
MethodDecorator hello
ClassDecorator2
ClassDecorator1
执行顺序的规律:
-
类装饰器是最后执行的,后写的类装饰器先执行。
-
方法和方法参数中的装饰器先执行参数。
-
方法和属性装饰器,谁在前面先执行谁。
4. 接口
接口用interface定义
接口可以在面向对象编程中表示为:行为的抽象,也可以用来描述:对象的形状。
接口就是把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类。
一个类可以继承另一个类并实现多个接口。
接口像插件一样是用来增强类的,而抽象类是具体类的抽象概念。
一个类可以实现多个接口,一个接口也可以被多个类实现,但一个类可以有多个子类,且只能有一个父类。
接口可以重名,类不可以重名。
interface中可以用分号或者逗号分割每一项,也可以什么都不加
4.1 对象的形状
接口可以用来描述对象的形状,少属性或者多属性都会报错。
类和接口一般首字母都要大写
interface Singable {
name: string;
sing(): void // sing方法,返回值是void
}
// let 变量user: 类型Singable = 对象
// 用Singable去约束user这个对象的形状,user必须要有name 和 sing方法。
let user: Singable = {
name: 'tangtangtang',
sing() {}
}
4.2 行为的抽象
接口可以在面向对象编程中表示为行为的抽象。
接口的特性: 同名的接口可以写多个,类型会自动合并。
实现用implements关键字
interface Singable {
sing(): void
}
interface Eatable {
eat(): void
}
// 一个类可以实现多个接口
class Person implements Singable,Eatable {
name: string
sing() {
console.log('唱歌')
}
// eat(): {} // 需要实现的接口包含eat方法,不实现就会报错
}
4.3 任意属性
无法预知知道有哪些新的属性,可以使用[propName:string]:any ,propName名字任意的。
interface Person2 {
readonly id: number; //只读id属性
name: string; // name属性
[key: string]: any // 代表任意属性。 [key: string]: any 即key类型是string ,值是any 。key的名字可以是任意的。
}
let p: Person2 = {
id: 1,
name: 'tangtang',
age: 20,
adress: 'beijing'
}
4.4 接口的继承
interface Speakable {
speak(): void
}
// 一个接口可以继承自另外一个接口。
// 子接口SpeakChinese 继承父接口Speakable
interface SpeakChinese extends Speakable {
speakChinese(): void
}
// 类ChineseMan实现SpeakChinese接口
class ChineseMan implements SpeakChinese {
speak() {
console.log("说话");
}
speakChinese() {
console.log("说中文");
}
}
4.5 readonly
用readonly定义只读属性。
可以避免由于多人协作或者项目较为复杂等因素造成对象的值被重写。
interface Person {
readonly id: number;//只读id属性
name: string
}
let user: Person = {
id: 10,
name: 'tangtangtang'
}
user.id = 20 ; // 会直接报错,因为id是只读属性
4.6 函数类型接口
函数类型接口的意思就是可以用接口去修饰/约束方法。对方法传入的参数和返回值进行约束。
interface Discount {
// 用接口约束方法
(price: number): number // 有一个price属性 类型是number ,返回也是number 这种写法表示是一个函数
}
const discount: Discount = (price: number): number => {
return price*.5;
}
4.7 可索引接口
可索引接口指对数组和对象进行约束
// UserInterface表示: 只要index的类型是number,那么值的类型必须是string
interface UserInterface {
[index: number]: string // 任意类型,index可以随便写,叫什么都可以
}
let arr: UserInterface = ['haha1','haha2'];
console.log(arr);
// UserInterface2表示:只要 index 的类型是 string,那么值的类型必须是 string
interface UserInterface2 {
[index: string]: string
}
let obj: UserInterface2 = {name: 'haha'};
console.log(obj);
4.8 类接口
类接口指如何用接口约束类
interface Spaeakable {
name: string;
speak(words: string): void
}
// 如果我们用类去实现接口,那么就要实现接口里面的属性和方法。
class Dog implements Speakable {
name: string;
speak(words: string): void {
console.log(words);
}
}
let dog = new Dog();
dog.speak('汪汪汪汪汪汪汪');
4.9 构造函数类型接口
在TS中,我们可以用interface来描述类,同时也可以用interface里面特殊的new()关键字来描述类的构造函数类型。
class Animal {
constructor(public name: string) {} // public name:string表示给类的实例增加一个name属性
}
// 修饰普通函数
// interface WithNameClass {
// (name: string): any //修饰普通函数,没有new关键字
// }
// 修饰构造函数
interface WithNameClass {
new(name: string): any // 修饰构造函数,加上new 关键字后就是用来描述类的构造函数
}
// let getName: WithNameClass = (name: string) => {} // 不加new,普通函数
// let getName: WithNameClass = Animal; // 加了new ,表示描述类的构造函数
// 函数createClass,WithNameClass 用来修饰钩子函数clazz
function createClass(clazz: WithNameClass,name: string) {
return new clazz(name);
}
let getName = createClass(Animal,'tangtangtang');
console.log(getName.name);
4.10 扩展
接口描述一个函数和描述一个对象的写法区别
interface Type1 {
(name: string): any; // 描述一个函数
age: number
}
interface Type2 {
a: (name: string) => any // 描述一个对象,对象的属性是a,a的值是函数
}
// 如果想给对象里增加一个属性age
let t: any = (name: string) => {}
t.age = 20;
let t1: Type1 = t;
let t2: Type2 = {
a: t1
}
5. 泛型
泛型Generics是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是: 类的实例成员、类的方法、函数参数和函数返回值。
5.1 泛型函数
为了更好的了解泛型的作用,我们可以先看下面的一个例子。
//创建一个长度为length的数组,值为value的数组,返回值是 Array<any>
function createArray(length: number, value: any): Array<any> {
let result: any = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
let result = createArray(3,'x'); // 长度为3,值为x
console.log(result); // ['x','x','x']
从上述例子中我们发现一个问题,不管我们传入什么类型的value,返回值的数组永远是any类型。
如果我们想要的效果是,我们预先不知道会传入什么类型,但是我们希望不管我们传入什么类型,返回的数组的值的类型应该和参数保持一致,那么这个时候就可以考虑泛型。
泛型是一个宽泛的类型,
T代表一个类型,泛型T作用域只限于函数内部使用。我们可以使用<>的写法,然后在里面传入一个变量T,用来表示后面函数需要用到的类型。
注意:T类似一个变量,其实就是Type的缩写,叫什么都可以,一般是一个大写字母。定义的时候不确定,调用的时候才确定。
function createArray2<T>(length: number, value: T): Array<T> {
let result: T[] = []; // T[]: T类型的数组 ,比如string<T> number<T>
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
let result2 = createArray2<string>(3,'x');
console.log(result2);
5.2 泛型类
泛型类即类里面也可以传泛型。
class MyArray<T> { // MyArray类添加泛型T,如果类传了泛型T那么T就会在类里面被使用
private list: T[] = []//声明一个私有变量list ,类型是T的数组,值默认为空数组
add(value: T) {
this.list.push(value);
}
getFirst(): T {
return this.list[0];
}
}
let array = new MyArray<number>();
array.add(1);
array.add(2);
array.add(3);
console.log(array)
console.log(array.getFirst()); //1
5.3 泛型接口
泛型接口可以用来约束函数。
// 例子1
interface Calculate1 { // Calculate1接口修饰一个函数
<T>(a: T,b: T): T // 函数,函数接收一个T泛型,参数a类型是T,参数b类型是T,最后返回T
}
let sum2: Calculate1 = function <T>(a: T, b: T): T {
// return a + b // error:Operator '+' cannot be aplied to type 'T' and 'T'。
//因为泛型T可以被定义为任意类型。比如说两个函数不能相加
return a;
};
sum2<number>(1, 2);
接口描述一个函数和描述一个对象的写法区别 见4.接口->4.10扩展
// 定义接口的时候也可以指定泛型
interface Calculate2<T> { //Calculate2接口的类型是T
(a: T,b: T): T
}
// let 变量:类型 = 值
let sum: Calculate<number> = function (a: number, b: number): number {
return a + b; // 可以相加 ,定义类型的时候就已经确定类型为number
};
sum(1,2);
5.4 多个类型参数
泛型可以写多个。
如果我们需要有多个未知的类型占位,那么我们可以定义任何的字母来表示不同的类型参数。
// 用泛型A,B约束函数,参数是个元组tuple,A B,返回[B,A]
function swap<A,B>(tuple: [A,B]): [B,A] {
return [tuple[1],tuple[0]]; // 相当于B,A
}
let swapped = swap<string, number>(['a', 1]);
console.log(swapped); // [ 1, 'a' ]
5.5 默认泛型类型
默认泛型类型类似于默认参数,我们可为泛型中的类型参数指定默认类型。如<T=number>
当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。
function createArray3<T=number>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
let result3 = createArray3<string>(3, 'x');// 如果没有写string ,那么默认就是number
console.log(result3);
// 默认参数或者叫默认泛型类型。
// 如果不传默认值,会报错,泛型类型需要一个类型参数
//1
interface T2<T=string> {
}
type T22 = T2;
//2
interface T1<T> {
}
type T21= T1<string>;
5.6 泛型约束
在函数中使用泛型的时候,由于预先并不知道泛型的类型,所以不能随意访问相应类型的属性或方法。
function logger<T>(val: T) {
console.log(val.length); //error: Property 'length' does not exist on type 'T'.
//因为泛型T不一定包含属性length,所以编译的时候报错了
return val;
}
综上我们可以对泛型进行约束,只允许这个函数传入那么包含length属性的变量。 这就是泛型约束
interface LengthWise {
length: number
}
function logger2<T extends LengthWise>(val: T) {
console.log(val.length);
return val;
}
注意:我们在泛型里面使用extends关键字代表的是泛型约束,需要和类的继承区分开。
class GrandFather {}
class Father extends GrandFather {}
class Child extends Father {}
function get<T extends Father>() {}
get(Father);//ok
get(Child);//ok
get(GrandFather);//ok ,思考为什么
//类型的兼容性:判断兼容不兼容, 跟extends继承没有一点关系 ,只看形状 有没有对应的属性
class GrandFather {
grandFather: string
}
class Father extends GrandFather {
father: string;
}
class Child extends Father {
child: string
}
//extends 不叫继承叫约束
//<T extends Father> T能赋值给Father, T是Father的子类型, 要满足Father的要求
function get<T extends Father>() {
}
let grandfather = new GrandFather();
let father = new Father();
let child = new Child();
father = grandfather; // error:类型GrandFather中缺少属性father,但类型Father中需要该属性。
grandfather = father; //ok
father = child; // ok 子类赋给父类。 因为父类需要的father属性,子类都有。
child = father //error: //父类赋给子类。因为子类需要的child属性,父类没有。
5.7 泛型类型类名
泛型类型别名type可以表达更复杂的类型。
// 通过type声明新的类型叫Cart[T],等于或者是一个对象,属性是list ,值是T类型的数组。或者是T类型的数组。
type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = {list:['1']};
let c2: Cart<number> = [1,2,3];
5.8 interface和type的区别
1.当我们需要使用基本类型(原始值)、联合类型或者元组类型的时候,类型别名会更合适。
// primitive
type Name = string;
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
// dom
let div = document.createElement("div");
type B = typeof div;
2.接口可以被重复定义,会被自动合并为单个接口,类型别名不可以重复定义
interface Point {
x: number;
}
interface Point {
y: number;
}
const point: Point = { x: 1, y: 2 };
3.接口可以扩展类型别名,同理,类型别名也可以扩展接口。但是两者实现扩展的方式不同
接口的扩展就是继承,通过 extends 来实现。类型别名的扩展就是交叉类型,通过&来实现。
// 接口扩展接口
interface PointX {
x: number;
}
interface Point extends PointX {
y: number;
}
// ----
// 类型别名扩展类型别名
type PointX = {
x: number;
};
type Point = PointX & {
y: number;
};
// ----
// 接口扩展类型别名
type PointX = {
x: number;
};
interface Point extends PointX {
y: number;
}
// ----
// 类型别名扩展接口
interface PointX {
x: number;
}
type Point = PointX & {
y: number;
};
4.类型别名不能被 extends和 implements,这时我们应该尽量使用接口代替类型别名。
// 类无法实现定义了联合类型的类型别名
type PartialPoint = { x: number } | { y: number };
// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint {
// Error
x = 1;
y = 2;
}
总之: 能用interface实现的不要用type实现。
5.9 泛型工具类型
5.9.1 typof关键词
typeof 关键词除了做类型保护,还可以从实现推出类型。
//先定义变量,再定义类型
let p1 = {
name: "hello",
age: 10,
gender: "male",
};
type People = typeof p1;
function getName(p: People): string {
return p.name;
}
getName(p1);
上面的例子就是使用 typeof 获取一个变量的类型。
5.9.2 keyof关键词
keyof 可以用来取得一个对象接口的所有 key 值。
interface Person {
name: string;
age: number;
gender: "male" | "female";
}
//type PersonKey = 'name'|'age'|'gender';
type PersonKey = keyof Person;
function getValueByKey(p: Person, key: PersonKey) {
return p[key];
}
let val = getValueByKey({ name: "hello", age: 10, gender: "male" }, "name");
console.log(val);
5.9.3 映射类型in
在定义的时候用 in 操作符去批量定义类型中的属性。
interface Person {
name: string;
age: number;
gender: "male" | "female";
}
//批量把一个接口中的属性都变成可选的
type PartPerson = {
[Key in keyof Person]?: Person[Key];
};
let p1: PartPerson = {};
5.9.4 索引访问操作符
使用 [] 操作符可以进行索引访问。
interface Person {
name: string;
age: number;
}
type x = Person["name"]; // x is string
5.9.5 infer关键字
在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。
infer推断,表示在extends条件语句中待推断的类型变量。
ReturnType获取函数类型的返回类型。
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
// infer R 推断的意思
以上代码中 infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。
function getUser() {
return { name:'tangtangtang',age:20 };
}
type GetUserType = typeof getUser; // typeof getUser 获取getUser这个函数的类型
type ReturnUser = ReturnType<GetUserType>; // 获取函数类型的返回类型
let u: ReturnUser = {
name:'haha',
age: 3
}
console.log(u) // { name: 'haha', age: 3 }
5.9.6 内置工具类型
Exclude<T, U> 从 T 可分配给的类型中排除 U。
Extract<T, U> 从 T 可分配的类型中提取 U。
NonNullable<T> 从 T 中排除 null 和 undefined。
ReturnType<T> 获取函数类型的返回类型。
InstanceType<T> 获取构造函数类型的实例类型。
- Exclude<T,U> 从 T 可分配给的类型中排除 U
type Exclude<T, U> = T extends U ? never : T;
type E = Exclude<string | number, string>;
let e: E = 10;
- Extract<T,U> 从 T 可分配给的类型中提取 U
type Extract<T, U> = T extends U ? T : never;
type E = Extract<string | number, string>;
let e: E = "1";
- NonNullable 从 T 中排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type E = NonNullable<string | number | null | undefined>;
let e: E = null;
- ReturnType 获取函数类型的返回类型。
type ReturnType<T extends (...args: any[]) => any> = T extends (
...args: any[]
) => infer R
? R
: any;
function getUserInfo() {
return { name: "hello", age: 10 };
}
// 通过 ReturnType 将 getUserInfo 的返回值类型赋给了 UserInfo
type UserInfo = ReturnType<typeof getUserInfo>;
const userA: UserInfo = {
name: "hello",
age: 10,
};
- Parameters 该工具类型主要是获取函数类型的参数类型。
type Parameters<T> = T extends (...args: infer R) => any ? R : any;
type T0 = Parameters<() => string>; // []
type T1 = Parameters<(s: string) => void>; // [string]
type T2 = Parameters<<T>(arg: T) => T>; // [unknown]
- Partial 可以将传入的属性由非可选变为可选。
type Partial<T> = { [P in keyof T]?: T[P] };
interface A {
a1: string;
a2: number;
a3: boolean;
}
type aPartial = Partial<A>;
const a: aPartial = {}; // 不会报错
- Required 可以将传入的属性中的可选项变为必选项,这里用了 -? 修饰符来实现。
interface Person {
name: string;
age: number;
gender?: "male" | "female";
}
/**
* type Required<T> = { [P in keyof T]-?: T[P] };
*/
let p: Required<Person> = {
name: "hello",
age: 10,
gender: "male",
};
- Readonly 通过为传入的属性每一项都加上 readonly 修饰符来实现。
interface Person {
name: string;
age: number;
gender?: "male" | "female";
}
//type Readonly<T> = { readonly [P in keyof T]: T[P] };
let p: Readonly<Person> = {
name: "hello",
age: 10,
gender: "male",
};
p.age = 11; //error
- Pick<T,K> Pick 能够帮助我们从传入的属性中摘取某些返回。
interface Todo {
title: string;
description: string;
done: boolean;
}
/**
* From T pick a set of properties K
* type Pick<T, K extends keyof T> = { [P in K]: T[P] };
*/
type TodoBase = Pick<Todo, "title" | "done">;
// =
type TodoBase = {
title: string;
done: boolean;
};
- Record<K,T> 构造一个类型,该类型具有一组属性 K,每个属性的类型为 T。可用于将一个类型的属性映射为另一个类型。Record 后面的泛型就是对象键和值的类型。
简单理解:K 对应对应的 key,T 对应对象的 value,返回的就是一个声明好的对象 但是 K 对应的泛型约束是keyof any 也就意味着只能传入 string|number|symbol
// type Record<K extends keyof any, T> = {
// [P in K]: T;
// };
type Point = "x" | "y";
type PointList = Record<Point, { value: number }>;
const cars: PointList = {
x: { value: 10 },
y: { value: 20 },
};
- Omit<K,T> 基于已经声明的类型进行属性剔除获得新类型
// type Omit=Pick<T,Exclude<keyof T,K>>
type User = {
id: string;
name: string;
email: string;
};
type UserWithoutEmail = Omit<User, "email">;// UserWithoutEmail ={id: string;name: string;}
};
6. 条件类型
6.1 定义条件类型
interface Fish {
name1:string;
}
interface Water {
name2:string;
}
interface Bird {
name3:string;
}
interface Sky {
name4:string;
}
// 类型type叫Condition , T extends Fish 即T是Fish的子类型 ,就返回Water否则就返回Sky
type Condition<T> = T extends Fish?Water:Sky;
let con:Condition<Fish> = {name2:'水'};
6.2 条件类型的分发
// T extends U 这里的T是裸着的T 即裸类型会分发,一个个传。。 不裸的就整个传
type Diff<T,U> = T extends U?never:T;
type R = Diff<'a'|'b'|'c'|'d' , 'a'|'b'|'c'>; // 裸类型
type R2 = 'd'; // 一个个传,a 是abc的子类型,返回never,b是abc的子类型,返回never ,c是abc的子类型,返回never ,d是abc的子类型,不是,返回T,T这时就是d ,最后总输出never|never|never|d ,就是d .
//例子2
type Filter<T,U> = T extends U?T:never;
type R3 = Filter<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>;
// R3= 'abc';
interface Fish {
name1:string;
}
interface Water {
name2:string;
}
interface Bird {
name3:string;
}
interface Sky {
name4:string;
}
// 这个例子中T即{ t: T } 不是裸着的T ,条件会整体传过去 这个时候的T 是Fish或者Bird ,所以输出Sky ,name4
type Condition2<T> = { t: T } extends { t: Fish} ? Water : Sky;
let con3: Condition2<Fish | Bird> = { name4:''};
总结: 裸类型分发,展开,一个个传。 非裸类型会整个一起传
6.3 内置条件类型
更新中................
7. 结构类型系统
7.1 接口的兼容性
7.2 基本类型的兼容性
7.3 类的兼容性
7.4 函数的兼容性
7.4.1 比较参数
7.4.2 比较返回值
7.5 函数参数的双向斜变
7.6 泛型的兼容性
7.7 枚举的兼容性
8. 模块和声明文件
8.1 全局模块
8.2 文件模块
8.3 声明文件
8.4 第三方声明文件
8.5 查找声明文件
更新中................