// 设置 "strictNullChecks": false
let num: number = 123;
num = undefined;
num = null;
// 但是更建议将 num 设置为联合类型
let num: number | undefined | null = 123;
num = undefined;
num = null;
// 枚举成员
enum Char {
// const member 常量枚举,会在编译阶段计算结果,以常量的形式出现在运行时环境
a,
b = Char.a,
c = 1 + 3,
// computed member 需要被计算的枚举成员,不会在编译阶段进行计算,会被保留到执行阶段
d = Math.random(),
e = "123".length,
// 在 computed member 后面的枚举成员,一定要赋一个初始值,否则报错
f = 1,
}
console.log(Char);
// 枚举 number
enum number {
a = 1,
b = 5,
c = 4,
d,
}
console.log(number); //打印出{1: "a", 4: "c", 5: "d", a: 1, b: 5, c: 4, d: 5}
// b赋初始值为5,c赋初始值为4,按照索引递增,d的索引就是5,索引相同时,后面的值覆盖前面的,所以5对应的 value 就是d
function add8(...rest: number[]): number;
function add8(...rest: string[]): string;
function add8(...rest: any[]): any {
let first = rest[0];
if (typeof first === "string") {
return rest.join("");
}
if (typeof first === "number") {
return rest.reduce((pre, cur) => pre + cur);
}
}
add8(1, 2, 3); // 6
add8("1", "2", "3"); // '123'
类
类属性和方法注意点
类属性都是实例属性,不是原型属性,而类方法都是原型方法
实例的属性必须具有初始值,或者在构造函数中初始化,除了类型为 any 的。
类的继承
派生类的构造函数必须包含“super”调用,并且访问派生类的构造函数中的 this 之前,必须调用“super"
class Dog {
constructor(name: string) {
this.name = name;
this.legs = 4; // 已经有默认值的只读属性是可以被重新初始化的
}
public name: string;
run() {}
private pri() {}
protected pro() {}
readonly legs: number = 3;
static food: string = "bones";
}
let dog = new Dog("jinmao");
// dog.pri() // 私有属性不能在实例中调用
// dog.pro() // 受保护的属性,不能在实例中调用
console.log(Dog.food); // 'bones'
class Husky extends Dog {
constructor(name: string, public color: string) {
super(name);
this.color = color;
// this.legs = 5 // 子类的构造函数中是不能对父类的只读属性重新初始化的
// this.pri() // 子类不能调用父类的私有属性
this.pro(); // 子类可以调用父类的受保护属性
}
protected age: number = 3;
private nickname: string = "二哈";
info(): string {
return this.age + this.nickname;
}
// color: string // 参数用了修饰符,可以直接定义为属性,这里就不需要了
}
let husky = new Husky("husky", "black");
husky.info(); // 如果调用的类的方法中有对类的私有属性和受保护属性的访问,这是不报错的。
console.log(Husky.food); // 'bones' 子类可以调用父类的静态属性
抽象类
只能被继承,不能被实例化的类。
在抽象类中可以添加共有的方法,也可以添加抽象方法,然后由子类具体实现
abstract class Animal {
eat() {
console.log("eat");
}
abstract sleep(): void; // 抽象方法,在子类中实现
}
// let animal = new Animal() // 会报错,抽象类无法创建实例
class Cat extends Animal {
constructor(public name: string) {
super();
}
run() {}
// 必须实现抽象方法
sleep() {
console.log("sleep");
}
}
let cat = new Cat("jiafei");
cat.eat();
接口类
类实现接口时,必须实现接口的全部属性,不过类可以定义自己的属性
接口不能约束类的构造函数,只能约束公有成员
interface Human {
// new (name:string):void // 接口不能约束类的构造函数
name: string;
eat(): void;
}
class Asian implements Human {
constructor(name: string) {
this.name = name;
}
name: string;
// private name: string // 实现接口时用了私有属性会报错
eat() {}
sleep() {}
}
接口继承类
相当于把类的成员抽象出来,只有类的成员结构,但是没有具体实现
接口抽离类成员时不仅抽离了公有属性,还抽离了私有属性和受保护属性,所以非继承的子类都会报错
被抽象的类的子类,也可以实现类抽象出来的接口,而且不用实现这个子类的父类已有的属性
class Auto {
state = 1;
// protected state2 = 0 // 下面的C会报错,因为C并不是 Auto 的子类,C只是实现了 Auto 抽象出来的接口
}
interface AutoInterface extends Auto {}
class C implements AutoInterface {
state = 1;
}
// 被抽象的类的子类,也可以实现类抽象出来的接口,而且不用实现父类的已有的属性
class Bus extends Auto implements AutoInterface {
// 不用设置 state ,Bus 的父类已经有了。
}
泛型
泛型函数
注意:用泛型定义函数类型时的位置不用,决定是否需要指定参数类型,见下面例子。
泛型函数例子
function log<T>(value: T): T {
console.log(value);
return value;
}
log<string[]>(["a", "b"]);
log([1, 2]); // 可以不用指定类型,TS会自动推断
// 还可以用类型别名定义泛型函数
//下面的定义不用指定参数类型
type Log = <T>(value: T) => T; // 不用指定参数类型,会自己推断
let myLog: Log = log;
//下面的定义必须指定参数类型
type Log<T> = (value: T) => T; // 如果这样用泛型定义函数类型,必须指定一个参数类型
let myLog: Log<string> = log;
class A {
constructor(p: number, q: number) {}
id: number = 1;
}
class B {
static s = 1;
constructor(p: number) {}
id: number = 2;
}
let aa = new A(1, 2);
let bb = new B(1);
// 两个实例完全兼容,静态成员和构造函数是不比较的
aa = bb;
bb = aa;
class A {
constructor(p: number, q: number) {}
id: number = 1;
private name: string = ""; // 只在A类中加这个私有属性,aa不兼容bb,但是bb兼容aa,如果A、B两个类中都加了私有属性,那么都不兼容
}
class B {
static s = 1;
constructor(p: number) {}
id: number = 2;
}
let aa = new A(1, 2);
let bb = new B(1);
aa = bb; // 不兼容
bb = aa; // 兼容
// A中有私有属性,C继承A后,aa和cc是相互兼容的
class C extends A {}
let cc = new C(1, 2);
// 两个类的实例是兼容的
aa = cc;
cc = aa;
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript();
// 如果想根据lang实例的类型,直接用lang.helloJava是不是存在来作为判断是会报错的,因为现在lang是Java和JavaScript这两种类型的联合类型
if (lang.helloJava) {
lang.helloJava();
} else {
lang.helloJavaScript();
}
return lang;
}
利用之前的知识可以使用类型断言解决
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript();
// 这里就需要用类型断言来告诉TS当前lang实例要是什么类型的
if ((lang as Java).helloJava) {
(lang as Java).helloJava();
} else {
(lang as JavaScript).helloJavaScript();
}
return lang;
}
类型保护第一种方法,instanceof
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript();
// instanceof 可以判断实例是属于哪个类,这样TS就能判断了。
if (lang instanceof Java) {
lang.helloJava();
} else {
lang.helloJavaScript();
}
return lang;
}
类型保护第二种方法, in 可以判断某个属性是不是属于某个对象
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript();
// in 可以判断某个属性是不是属于某个对象 如上helloJava和java都能判断出来
if ("java" in lang) {
lang.helloJava();
} else {
lang.helloJavaScript();
}
return lang;
}
类型保护第三种方法, typeof 类型保护,可以帮助我们判断基本类型
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript();
// x也是联合类型,typeof类型保护,可以判断出基本类型。
if (typeof x === "string") {
x.length;
} else {
x.toFixed(2);
}
return lang;
}
类型保护第四种方法,通过创建一个类型保护函数来判断对象的类型
类型保护函数的返回值有点不同,用到了 is ,叫做类型谓词
function isJava(lang: Java | JavaScript): lang is Java {
return (lang as Java).helloJava !== undefined;
}
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new JavaScript();
// 通过创建一个类型保护函数来判断对象的类型
if (isJava(lang)) {
lang.helloJava();
} else {
lang.helloJavaScript();
}
return lang;
}
type TypeName<T> = T extends string
? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends undefined
? "undefined"
: T extends Function
? "function"
: "object";
type T1 = TypeName<string>; // 得到的类型即: type T1 = "string"
type T2 = TypeName<string[]>; // 得到的类型即:type T2 = "object"
分布式条件类型
(A | B) extends U ? X : Y 等价于 (A extends U ? X : Y) | (B extends U ? X : Y)
// 接上文
type T3 = TypeName<string | string[]>; // 得到的类型即:type T3 = "string" | "object"