主讲:云隐
一、TS 的基础概念
什么是 TS
它是 JS 的一个超集,在原有的语法基础上,添加强类型并切换为基于类的面向对象语言。
对比原理
- 面向项目:
TS:面向解决大型的复杂项目、架构、代码维护复杂场景;JS:脚本化语言,用于面向简单页面场景;
- 自主检测:
TS:编译时,主动发现并纠正错误;JS:运行时,执行报错;
- 类型检测:
TS:强类型语言,支持动态和静态的类型检测;JS:弱类型语言,无静态类型选项;
- 运行流程:
TS:依赖编译,依靠编译打包实现在浏览器端的运行;JS:可直接在浏览器端运行;
- 复杂特性:
TS:模块化、接口、泛型;
安装运行
// 安装
npm install -g typescript
// 查看版本号
tsc -v
// 运行 ts 文件
tsc xxx.ts
// 面试点:所有类型的检测和纠错 - 编译时 => 工程化
面试点: 所有类型的检测和纠错 —— 编译时;
TS 基础类型和语法
string:字符串number:数字boolean:布尔null:nullundefined:undefinedarray:数组
// ES
let isEnable = false;
let className = 'zhaowa';
let classNum = 2;
let u = undefined;
let n = null;
let classArr = ['basic', 'execute'];
// TS
let isEnable: boolean = false;
let className: string = 'zhaowa';
let classNum: number = 2;
let u: undefined = undefined;
let n: null = null;
// 统一方式 & <>方式
let classArr: string[] = ['basic', 'execute'];
let classArr: Array<string> = ['basic', 'execute'];
tuple:元组- 当访问一个越界的元素会报错;
let tupleType: [string, boolean] = ['basic', false];
console.log(tupleType); // [ 'basic', false ]
console.log(tupleType[0]); // 'basic'
console.log(tupleType[2]); // 当访问一个越界的元素会报错
-
enum:枚举- 数字型枚举:默认从
0开始,依次递增; - 字符串类型枚举;
// 数字型枚举:默认从 0 开始,依次递增 enum Score { BAD, // 0 NG, // 1 GOOD, // 2 PERFECT // 3 } let score: Score = Score.BAD; // 0 // 字符串类型枚举 enum Score { BAD = 'BAD', NG = 'NG', GOOD = 'GOOD', PERFECT = 'PERFECT' } let score: Score = Score.NG; // 'NG'- 反向映射:
// 反向映射 enum Score { BAD, // 0 NG, // 1 GOOD, // 2 PERFECT // 3 } let scoreName = Score[0]; // 'BAD' let scoreValue = Score['BAD']; // 0- 异构:
// 异构 enum Enum { A, // 0 B, // 1 C = 'C', D = 'D', E = 8, F // 9 }面试题: 异构类型每一项的枚举值 =>
js本质实现(手写一个异构枚举的实现)let Enum; (function (Enum) { // 正向 Enum['A'] = 0; Enum['B'] = 1; Enum['C'] = 'C'; Enum['D'] = 'D'; Enum['E'] = 8; Enum['F'] = 9; // 逆向 Enum[0] = 'A'; Enum[1] = 'B'; Enum[8] = 'E'; Enum[9] = 'F'; })(Enum || (Enum = {})); - 数字型枚举:默认从
-
any:绕过所有类型检查,导致取消类型检测和编译筛查;
// any:绕过所有类型检查 => 类型检测和编译筛查取消
let anyValue: any = 123;
anyValue = 'anyValue';
anyValue = false;
let value1: boolean = anyValue;
unknown:绕过赋值检查,但是禁止更改传递;- 可以把
unknown类型的值赋值给unknown或者any类型,但是不可以赋值给除了any/unknown的其它类型;
- 可以把
// unknown:绕过赋值检查 => 禁止更改传递
let unknownValue: unknown;
unknownValue = true;
unknownValue = 123;
unknownValue = 'unknownValue';
let value1: unknown = unknownValue; // OK
// 可以把 unknown 类型的值赋值给 any 类型
let value2: any = unknownValue; // OK
// 不可以赋值
let value3: string = unknownValue; // NOK
let value3: null = unknownValue; // NOK
let value3: undefined = unknownValue; // NOK
void:声明返回为空(没有return);
// void:声明返回为空
function voidFunction(): void {
console.log('void');
}
never:永不能执行完或者永远error;
// never:永不能执行完 or 永远 error
function errorGen(msg: string): never {
throw new Error(msg);
}
function infiniteLoop(): never {
while (true) {
// 业务逻辑
}
}
object / Object / {}:对象
// object:非原始类型
interface ObjectConstructor {
create(o: object | null): any;
}
const proto = {};
Object.create(proto);
Object.create(null);
Object.create(undefined); // Error
// Object
// Object.prototype 上的属性
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
// ...
}
// 定义了 Object 类属性
interface ObjectConstructor {
new (value: any): Object;
readonly prototype: Object;
// ...
}
// {}:定义空属性对象
const obj = {};
obj.prop = 'props'; // NOK
obj.toString(); // OK
二、接口 - interface
对行为模块的抽象,具体的行为是由类来实现。
接口的定义和使用
赋值的时候,变量的形状必须和接口的形状保持一致。
// 以下接口的使用都不可以
// 1、定义的变量比接口少一些属性
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom'
};
// 2、定义的变量比接口多一些属性
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
可选属性 - ?
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
let tom2: Person = {
name: 'Tom',
age: 22
};
任意属性
interface Class {
readonly name: string;
time?: number;
[propName: string]: any; // 任意可添加属性
}
const c1: Class = { name: 'JS' };
const c2: Class = { name: 'browser', time: 1 };
const c3: Class = { name: 'ts', level: 1 };
只读属性 - readonly
interface Person {
readonly id: number; // id 是只读的,不能被改变
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527; // 报错
注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。
interface Person {
readonly id: number; // id 为只读属性,没有给 id 赋值
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
tom.id = 89757; // 报错,只读属性不能再次被改变
// 描述对象内容
interface Class {
name: string;
time: number;
}
let zhaowa: Class = {
name: 'typescript',
time: 2
};
// readonly 只读
// ? 可选属性
interface Class {
readonly name: string;
time: number;
isEnd?: false;
}
面试题:JS 的引用类型操作不同 < = > const
let arr: number[] = [1, 2, 3, 4];
// ReadonlyArray<T> 是一个真正的只读数组,它没有任何可以更改数组的方法
let ro: ReadonlyArray<number> = arr; // ro 是只读类型的
// readonly 声明的数据,是不可以被改变的
ro[0] = 12; // 赋值 - Error
ro.push(5); // 增加 - Error
ro.length = 10; // 长度改写 - Error
// 这里可以理解为跟 const 一样,arr 数组中的数据可以改,但是不能改变引用的地址
arr = ro; // 覆盖 - Error
三、交叉类型 - &
合并
// 合并
interface A {
inner: D;
}
interface B {
inner: E;
}
interface C {
inner: F;
}
interface D {
d: boolean;
}
interface E {
e: string;
}
interface F {
f: number;
}
// 声明一个新的类型(ABC),类型是 接口A、接口B、接口C 的合并
type ABC = A & B & C; // type:定义一个类型,&:类型合并
// 接口的交叉合并,同类型的放在一起,不同类型的平铺开
let abc: ABC = {
inner: {
d: false,
e: 'className',
f: 5
}
};
// 好处:在接口复杂,嵌套比较深的情况下,可以拆分开来声明,最后做合并
合并冲突
// 合并冲突
interface A {
c: string;
d: string;
}
interface B {
c: number;
e: string;
}
type AB = A & B;
let ab: AB = {
c: 'c', // 不能将类型“string”分配给类型“never”
d: 'd',
e: 'e'
};
// 合并的关系是 '且' => c - never
四、断言
断言是类型的声明和转换(和编译器做了一个告知交流)。
- 特点:编译时作用;
声明形式
- 两种声明形式:尖括号形式、
as声明形式;- 两种形式是等价的;
- 但是,在
TypeScript里使用JSX时,只有as语法断言是被允许的。
// 1、尖括号形式
let anyValue: any = 'hi zhaowa';
// 执行到 anyValue 的时候,此时的 anyValue 为字符串类型
let anyLength: number = (<string>anyValue).length;
// 2、as 声明
let anyValue: any = 'hi zhaowa';
let anyLength: number = (anyValue as string).length;
非空断言
- 非空断言:
!,断言操作对象是非null和非undefined类型;
function sayHello(name: string | undefined) {
// let sname: string = name; // 这种写法会报错
// 可以简单加个条件判断
// let sname: string;
// if (name) {
// sname = name;
// }
// 或者使用非空断言操作符 !
let sname: string = name!;
}
// 非空判断 - 只确定不是空
type ClassTime = () => number;
const start = (ClassTime: ClassTime | undefined) => {
// 业务逻辑
// if (额外判断逻辑) {
let time = ClassTime!(); // 具体类型待定,但是非空确认
// }
};
面试题:
const tsClass: number | undefined = undefined;
// 在声明变量时,只是打声招呼说 tsClass 是非空的,在编译时会不处理这里,但是实际转义完后这里还是会存在 undefined 情况的
// 所以,【不建议】在赋值的时候做非空断言
// 非空断言尽量在逻辑功能块里使用,而不是在赋值的时候直接非空断言
const zhaowa: number = tsClass!;
console.log(zhaowa); // undefined
// 严格模式下,会被转义成
('use strict');
const tsClass = undefined;
const zhaowa = tsClass;
console.log(zhaowa); // undefined
肯定断言
肯定化保证赋值。
let score: number;
startClass();
console.log('' + score); // 使用前赋值,报错 - 在赋值前使用了变量“score”
function startClass() {
score = 5;
}
// 肯定断言 - 肯定化保证赋值
let score!: number; // 提前告知,score 是有值的
startClass();
console.log('' + score);
function startClass() {
score = 5;
}
五、类型守卫 - 语法规范范围内,额外的确认
多态 - 多种状态(多种类型)
in - 定义属性场景下内容的确认
in的是【属性】
// in - 定义属性场景下内容的确认
interface Teacher {
name: string;
courses: string[];
}
interface Student {
name: string;
startTime: Date;
}
type PeopleClass = Teacher | Student;
function startCourse(cls: PeopleClass) {
// 使用类型守卫 in 判断
if ('courses' in cls) {
console.log('Courses: ' + cls.courses);
}
if ('startTime' in cls) {
console.log('startTime: ' + cls.startTime);
}
}
startCourse({
name: 'teacher',
// courses: ['123']
startTime: new Date('2022-02-11')
});
typeof / instanceof - 类型分类场景下的身份确认
typeof 的使用
// typeof 的使用
function className(name: string, score: string | number) {
// 使用 typeof 判断类型
if (typeof score === 'number') {
return 'teacher: ' + name + ', ' + score + ' 分';
}
if (typeof score === 'string') {
return 'student: ' + name + ', ' + score + ' 分';
}
}
const classValue1 = className('tom', 80);
console.log(classValue1); // teacher: tom, 80 分
const classValue2 = className('tom', '90');
console.log(classValue2); // student: tom, 90 分
instanceof 的使用
instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上;
class Teacher {
constructor() {
this.name = 'teacher';
this.courses = ['js', 'ts'];
}
}
const t = new Teacher();
console.log(t); // Teacher { name: 'teacher', courses: [ 'js', 'ts' ] }
console.log(t.__proto__ === Teacher.prototype); // true
// instanceof 的使用
interface Teacher {
name: string;
courses: string[];
}
interface Student {
name: string;
startTime: Date;
}
class MyTeacher implements Teacher {
name: string;
courses: string[];
constructor(name: string, courses: string[]) {
this.name = name;
this.courses = courses;
}
}
class MyStudent implements Student {
name: string;
startTime: Date;
constructor(name: string, startTime: Date) {
this.name = name;
this.startTime = startTime;
}
}
type PeopleClass = Teacher | Student;
const getName = (cls: PeopleClass) => {
if (cls instanceof MyTeacher) {
return cls.courses;
}
if (cls instanceof MyStudent) {
return cls.startTime;
}
};
let t1 = new MyTeacher('tom', ['js', 'ts']);
const value1 = getName(t1);
console.log(value1); // [ 'js', 'ts' ]
let s1 = new MyStudent('jack', new Date());
const value2 = getName(s1);
console.log(value2); // 2022-04-15T18:25:39.531Z
自定义类型
- 自定义守卫通过
{形参} is {类型}的语法结构;
// 自定义类型
// cls 是 Teacher 类型 或者 Student 类型,判断当前函数是不是 Teacher
const isTeacher = function (cls: Teacher | Student): cls is Teacher {
return 'courses' in cls;
};
const getName = (cls: Teacher | Student) => {
if (isTeacher(cls)) {
return cls.courses;
}
};
const value = getName({
name: 'teacher',
courses: ['js', 'ts']
});
console.log(value); // [ 'js', 'ts' ]
六、 TS 进阶
函数重载
- 函数重载主要承载的功能是 复用 —— 对实际数据的类型做不同的匹配;
// Combinable 复合类型
type Combinable = number | string;
class Course {
start(name: number, score: number): number;
start(name: string, score: string): string;
start(name: string, score: number): string;
start(name: number, score: string): string;
start(name: Combinable, score: Combinable) {
if (typeof name === 'number' && typeof score === 'number') {
return score;
}
if (typeof name === 'string' && typeof score === 'number') {
return 'student: ' + name + ', ' + score + ' 分';
}
}
}
const course = new Course();
const value = course.start('云隐', 5);
console.log(value); // student: 云隐, 5 分
泛型 - 复用
- 让模块可以支持多种类型数据 —— 让类型声明和值一样,可以被赋值和传递;
// 返回值类型声明为 string
function startClass<T, U>(name: T, score: U): string {
return `姓名:${name},分数:${score}`;
}
// 或者使用 as 断言
function startClass<T, U>(name: T, score: U): T {
return (name + String(score)) as unknown as T;
}
const value = startClass<String, Number>('云隐', 5);
console.log(value); // 云隐5
// T、U、K - 键值
// V - 值
// E - 节点、元素
装饰器 - decorator
装饰器分类
- 常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器;
- 装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参);
tsconfig.json 配置
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
类装饰器
类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于构造函数,可以用来监视、修改或者替换类定义。
- 参数:类的构造函数(唯一的参数);
- 类装饰器:普通装饰器(无法传参)
function logClass(params: Function): void {
// params 就是当前类
console.log(params);
params.prototype.apiUrl = '动态扩展的属性';
params.prototype.run = function (): void {
console.log('我是一个 run 方法');
};
}
@logClass
class HttpClient {
constructor() {}
getData() {}
}
const http: any = new HttpClient();
console.log(http.apiUrl);
http.run();
- 类装饰器:装饰器工厂(可传参)
function logClass(params: string): Function {
return function (target: Function): void {
// params:传入的参数
// target:当前类
// console.log(target); // [class HttpClient]
// console.log(params); // http://www.baidu.com/api
target.prototype.apiUrl = params;
};
}
@logClass('http://www.baidu.com/api')
class HttpClient {
constructor() {}
getData() {}
}
let http: any = new HttpClient();
console.log(http.apiUrl);
- 类装饰器:重载构造函数
- 下面是一个重载构造函数的例子;
- 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数;
- 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明;
function logClass(target: any) {
return class extends target {
apiUrl = '我是修改后的数据';
getData(): void {
this.apiUrl += '!';
console.log(this.apiUrl);
}
};
}
@logClass
class HttpClient {
public apiUrl: string | undefined;
constructor() {
this.apiUrl = '我是构造函数里面的 apiUrl';
}
getData(): void {
console.log(this.apiUrl);
}
}
const http = new HttpClient();
http.getData(); // 我是修改后的数据!
属性装饰器
属性装饰器表达式会在运行时当做函数被调用,传入下列 2 个参数:
- 对于 静态成员 来说是类的 构造函数,对于 实例成员 是类的 原型对象;
- 成员的名字;
function logProperty(params: string) {
return function (target: any, attrName: string) {
console.log(params); // http://www.baidu.com
console.log(target); // 静态成员 - 构造函数;实例成员 - 原型对象 {}
// 此处是 类的原型对象,相当于 xxx.prototype
console.log(attrName); // 成员的名字:apiUrl
target[attrName] = params;
};
}
class HttpClient {
@logProperty('http://www.baidu.com')
public apiUrl: string | undefined;
constructor() {}
getData(): void {
console.log(this.apiUrl);
}
}
const http = new HttpClient();
http.getData(); // http://www.baidu.com
方法装饰器
方法装饰器会被应用到方法的属性描述符上,可以用来监视、修改或者替换方法的定义。
方法装饰器会在运行时传入下列 3 个参数:
- 对于 静态成员 来说是类的 构造函数,对于 实例成员 是类的 原型对象;
- 成员的名字;
- 成员的属性描述符;
// 方法装饰器1 - 扩展当前类的属性和方法
function get(params: string) {
return function (target: any, methodName: string, desc: any) {
console.log(params); // http://www.baidu.com
console.log(target); // 静态成员 - 构造函数;实例成员 - 原型对象 {}
// 此处是 类的原型对象,相当于 xxx.prototype
console.log(methodName); // 成员的名字:getData
console.log(desc); // 属性描述符
/*
{
value: [Function: getData],
writable: true,
enumerable: false,
configurable: true
}
*/
// 扩展当前类的属性和方法
target.apiUrl = 'xxx';
target.run = function (): void {
console.log('run');
};
};
}
class HttpClient {
public url: string | undefined;
constructor() {}
@get('http://www.baidu.com')
getData(): void {
console.log(this.url);
}
}
const http: any = new HttpClient();
console.log(http.apiUrl); // xxx
http.run(); // run
// 方法装饰器2 - 修改装饰的方法
function get(params: string) {
return function (target: any, methodName: string, desc: any) {
console.log(params); // http://www.baidu.com
console.log(target); // 静态成员 - 构造函数;实例成员 - 原型对象 {}
// 此处是 类的原型对象,相当于 xxx.prototype
console.log(methodName); // 成员的名字:getData
console.log(desc); // 属性描述符
/*
{
value: [Function: getData],
writable: true,
enumerable: false,
configurable: true
}
*/
// 修改装饰的方法 - 可以通过 desc 中的 value 修改
// 实现目标:把装饰器方法里面传入的所有参数改为 string 类型
// 1、保存当前的方法
const oldMethod = desc.value;
// 2、修改原始的方法
desc.value = function (...args: any[]) {
console.log('args ==== ', args); // [ 123, 'xxx' ]
args = args.map(value => String(value));
// 3、调用原始的方法
oldMethod.apply(this, args);
};
};
}
class HttpClient {
public url: string | undefined;
constructor() {}
@get('http://www.baidu.com')
getData(...args: any[]): void {
console.log(args);
console.log('我是 getData 里面的方法');
}
}
const http: any = new HttpClient();
http.getData(123, 'xxx');
/*
输出结果:
[ '123', 'xxx' ]
我是 getData 里面的方法
*/
方法参数装饰器
参数装饰器表达式在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入下列 3 个参数:
- 对于 静态成员 来说是类的 构造函数,对于 实例成员 是类的 原型对象;
- 成员的名字;
- 参数在函数参数列表中的索引;
function logParams(params: any) {
return function (target: any, methodName: any, paramsIndex: any) {
console.log(params); // xxxxx
console.log(target); // {}
console.log(methodName); // getData
console.log(paramsIndex); // 0
target.apiUrl = params;
};
}
class HttpClient {
public url: string | undefined;
constructor() {}
getData(@logParams('xxxxx') uuid: any) {
console.log('我是 getData 里面的方法');
console.log(uuid); // 123456
}
}
let http: any = new HttpClient();
http.getData(123456);
console.log(http.apiUrl); // xxxxx
装饰器的执行顺序
属性装饰器 => 方法装饰器 => 参数装饰器 => 类装饰器- 如果有多个同样的装饰器,它会先执行后面的;
// 类装饰器
function logClass1(params: any) {
return function (target: any) {
console.log('类装饰器1');
};
}
function logClass2(params: any) {
return function (target: any) {
console.log('类装饰器2');
};
}
// 属性装饰器
function logProperty(params?: string) {
return function (target: any, attrName: any) {
console.log('属性装饰器');
};
}
// 方法装饰器
function logMethod(params?: any) {
return function (target: any, methodName: any, desc: any) {
console.log('方法装饰器');
};
}
// 参数装饰器
function logParams1(params?: any) {
return function (target: any, methodName: any, paramsIndex: any) {
console.log('参数装饰器1');
};
}
function logParams2(params?: any) {
return function (target: any, methodName: any, paramsIndex: any) {
console.log('参数装饰器2');
};
}
@logClass1('xxx-1')
@logClass2('xxx-2')
class HttpClient {
@logProperty()
public url: string | undefined;
constructor() {}
@logMethod()
getData() {
return true;
}
setData(@logParams1() attr1: any, @logParams2() attr2: any) {
console.log('我是 getData 里面的方法');
}
}
let http: any = new HttpClient();
/*
输出结果:
属性装饰器
方法装饰器
参数装饰器2
参数装饰器1
类装饰器2
类装饰器1
*/
补充知识点
类
TS 中类的定义
class Person {
name: string; // 属性,前面省略了 public 关键字
constructor(name: string) {
// 构造函数,实例化类的时候触发的方法
this.name = name;
}
getName(): string {
return this.name;
}
setName(name: string) {
this.name = name;
}
run(): void {
console.log(`${this.name}在运动`);
}
}
const p = new Person('张三');
p.run(); // 张三在运动
console.log(p.getName()); // 张三
p.setName('李四');
console.log(p.getName()); // 李四
TS 中实现继承,使用关键字 extends、super
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
run(): void {
console.log(`${this.name}在运动`);
}
}
const p = new Person('张三');
p.run(); // 张三在运动
class Web extends Person {
constructor(name: string) {
super(name); // 初始化父类的构造函数
}
}
const w = new Web('李四');
w.run(); // 李四在运动
TS 中继承的探讨:父类的方法和子类的方法一致时,使用子类;
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
run(): void {
console.log(`${this.name}在运动`);
}
}
const p = new Person('张三');
p.run(); // 张三在运动
class Web extends Person {
constructor(name: string) {
super(name); // 初始化父类的构造函数
}
run(): void {
console.log(`${this.name}在运动 - 子类`);
}
work(): void {
console.log(`${this.name}在工作`);
}
}
const w = new Web('李四');
w.run(); // 李四在运动 - 子类
类里面的修饰符
TypeScript 里面定义属性的时候给我们提供了三种修饰符:
public:公有,在类里面、子类和类外面可以访问;protected:保护类型,在类里面和子类可以访问,在类外面无法访问;private:私有,只有在类里面可以访问,子类和类外面无法访问;- 属性如果不加修饰符,默认就是公有(
public);
class Person {
public name: string; // 公有属性
protected age: number; // 保护类型
private sex: string; // 私有属性
constructor(name: string, age: number, sex: string) {
this.name = name;
this.age = age;
this.sex = sex;
}
run(): string {
return `${this.name}在运动`;
}
getAge(): void {
console.log(`父类-年龄:${this.age}`);
}
getSex(): void {
console.log(`父类-性别:${this.sex}`);
}
}
class Web extends Person {
constructor(name: string, age: number, sex: string) {
super(name, age, sex); // 初始化父类的构造函数
}
work(): void {
console.log(`${this.name}在工作`);
}
run(): string {
return `${this.name}在运动 - 子类`;
}
getAge(): void {
console.log(`子类-年龄:${this.age}`);
}
getSex(): void {
// console.log(`子类-性别:${this.sex}`); // 报错,属性“sex”为私有属性,只能在类“Person”中访问
}
}
let w = new Web('李四', 22, '男');
w.work(); // 李四在工作,【公有属性在子类可以访问】
w.getAge(); // 子类-年龄:22,【保护类型在子类可以访问】
w.getSex();
// 类外面可以访问公有属性
let p = new Person('哈哈', 22, '女');
console.log(p.name); // 哈哈
p.getAge(); // 父类-年龄:22,【保护类型在父类可以访问】
p.getSex(); // 父类-性别:女,【私有属性在父类可以访问】
// 类外面无法访问保护类型
// console.log(p.age); // 报错,属性“age”受保护,只能在类“Person”及其子类中访问
// console.log(p.sex); // 报错,属性“sex”为私有属性,只能在类“Person”中访问
静态属性和静态方法,使用 static 修饰
class Person {
public name: string;
public age: number = 20;
// 静态属性
static sex: string = '男';
constructor(name: string) {
this.name = name;
}
// 实例方法
run(): void {
console.log(`${this.name}在运动`);
}
work(): void {
console.log(`${this.name}在工作`);
}
// 静态方法 - 静态方法里面无法直接调用类里面的属性
static print() {
// console.log(`我是公有属性:${this.name}`); // 报错
console.log(`我是静态属性:${this.sex}`);
}
}
var p = new Person('张三');
p.run(); // 张三 在运动
// 直接通过【类型.xxx】调用静态方法
Person.print(); // 我是静态属性:男
多态
- 父类定义一个方法不去实现,让继承它的子类去实现,每一个子类有不同的表现;
- 多态属于继承;
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat(): void {
console.log('父类 - 吃的方法');
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
eat(): void {
console.log(`${this.name}吃肉`);
}
}
class Cat extends Animal {
constructor(name: string) {
super(name);
}
eat(): void {
console.log(`${this.name}吃老鼠`);
}
}
抽象类 - abstract
TypeScript中的抽象类,它是提供其他类继承的基类,不能直接被实例化;- 用
abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现; abstract抽象方法只能方法抽象类里面;- 抽象类和抽象方法用来定义标准;
- 例如:
Animal这个类要求它的子类必须包含eat方法;
- 例如:
// 使用 abstract 定义抽象类
abstract class Animal {
public name: string;
constructor(name: string) {
this.name = name;
}
// 使用 abstract 定义抽象方法,抽象方法必须要在抽象类中
abstract eat(): void;
run(): void {
console.log('其他方法在子类中可以不实现');
}
}
// const a = new Animal(); // 报错,无法创建抽象类的实例。
class Dog extends Animal {
constructor(name: string) {
super(name);
}
// 抽象类的子类必须实现抽象类里面的抽象方法
eat(): void {
console.log(`${this.name}吃粮食`);
}
}
const dog = new Dog('二哈');
dog.eat(); // 二哈吃粮食
class Cat extends Animal {
constructor(name: string) {
super(name);
}
eat(): void {
console.log(`${this.name}吃老鼠`);
}
}
const cat = new Cat('小花');
cat.eat(); // 小花吃老鼠
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种。
联合类型使用 | 分隔每个类型。
let myFavoriteNumber: string | number;
function getLength(something: string | number): number {
return something.length; // 报错,length 不是 string 和 number 的共有属性,所以会报错
}
function getLength(something: string | number): string {
return something.toString(); // OK
}
接口
函数类型
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
// 定义函数型接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
// 使用
let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
let result = source.search(subString);
return result > -1;
};
// 或者
let mySearch: SearchFunc;
mySearch = function (src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
};
可索引的类型
// 对数组的约束
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ['Bob', 'Fred'];
let myStr: string = myArray[0]; // 'Bob'
// 对对象的约束
interface UserObj {
[index: string]: string;
}
const obj: UserObj = {
name: '张三'
};
类类型 - 对类的约束,和抽象类有点相似
interface Animal {
name: string;
eat(str: string): void;
}
// implements 实现接口
class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat(str: string): void {
console.log(`${this.name}吃${str}`);
}
}
const dog = new Dog('二哈');
dog.eat('肉'); // 二哈吃肉
class Cat implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat(str: string): void {
console.log(`${this.name}吃${str}`);
}
}
const cat = new Cat('小花');
cat.eat('粮食'); // 小花吃粮食
接口扩展:接口可以继承接口
interface Animal {
eat(str: string): void;
}
// 接口 Person 继承了 接口 Animal
interface Person extends Animal {
work(): void;
}
class Web implements Person {
public name: string;
constructor(name: string) {
this.name = name;
}
work(): void {
console.log(`${this.name}在加班`);
}
eat(str: string): void {
console.log(`${this.name}吃${str}`);
}
}
const w = new Web('程序员');
w.work(); // 程序员在加班
w.eat('夜宵'); // 程序员吃夜宵