第一章 TypeScript4简介
TypeScript 是由微软开发的一款开源的编程语言,TypeScript 是 Javascript 的超集,遵循最新的 ES6、ES5 规范,TypeScript 扩展了 JavaScript 的语法。TypeScript 更像后端 Java、C#这样的面向对象语言,可以让 JavaScript 开发大型企业项目。谷歌也在大力支持 Typescript 的推广,谷歌的 angular2.x+ 就是基于 Typescript 语法,最新的 Vue 、React 也可以集成 TypeScript。Nodejs 框架中的 Nestjs、midway 中用的就是 TypeScript 语法。
一张图描述 TypeScript 和 JavaScript 之前的关系:
个人感觉它俩之间的关系有点类似 C 和 C++ 之间的关系,语法风格更类似 Java、C# 。
打开CMD命令行,输入以下代码:
npm install -g typescript@4.1.2
打开CMD命令行,输入以下代码:
mkdir typescript-demo
cd typescript-demo
tsc --init
开发工具选用: Visual Studio Code,已安装汉化插件
开发工具版本: VSCodeSetup-x64-1.51.1.exe
开发环境版本: node-v14.15.0-x64.msi
开发工具使用:
按住快捷键 CTRL
+ SHIFT
+ ~
调出当前项目的终端,我们需要在终端中输入命令,来执行 js目录
中的已经编译好的代码。
变量格式一:
let 变量名: 变量类型 = 初始化值;
变量格式二:
let 变量名: 变量类型 | undefined;
变量名 = 变量值;
let flag: boolean = true;
console.log(flag);
整数型:
let num: number = 123;
console.log(num);
浮点型:
let num: number = 3.1415926;
console.log(num);
let str: string = "Hello,TypeScript";
console.log(str);
第一种定义数组的方式:以数字类型数组为例
let arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
console.log(arr);
第二种定义数组的方式:以数字类型数组为例
let arr: Array<number> = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
console.log(arr);
2.5、元组类型
元组属于数组的一种,元组中的元素可以不必全部保持类型一致!
let user: [number, string];
let userId = 10086;
let userName = "Nick";
let randomBoolean = true;
user = [userId, userName]; // 正确
user = [userId, randomBoolean]; // 错误
2.6、枚举类型
枚举类型的介绍:
随着计算机的不断普及,程序不仅只用于数值计算,还更广泛地用于处理非数值的数据。
例如:性别、月份、星期几、颜色、单位名、学历、职业等,都不是数值数据。
在其它程序设计语言中,一般用一个数值来代表某一状态,这种处理方法不直观,易读性差。
如果能在程序中用自然语言中有相应含义的单词来代表某一状态,则程序就很容易阅读和理解。
也就是说,事先考虑到某一变量可能取的值,尽量用自然语言中含义清楚的单词来表示它的每一个值,这种方法称为枚举方法,用这种方法定义的类型称枚举类型。
枚举类型的定义:
enum 枚举名 {
标识符[= 整型常数/字符串],
标识符[= 整型常数/字符串],
...
标识符[= 整型常数/字符串],
};
枚举类型的示例:
enum Flag {
success,
error,
overtime
};
let s: Flag = Flag.overtime;
console.log(s);//2
代码解读:如果标识符没有赋值,它的值就是下标。
enum Flag {
success = 200,
error = 404,
overtime = 500
};
let s: Flag = Flag.overtime;
console.log(s);//500
代码解读:如果标识符已经赋值,它的值就是被赋的值。
enum Flag {
success,
error = 100,
overtime
};
let s: Flag = Flag.overtime;
console.log(s);//101
代码解读:如果标识符没有赋值,它的值就是下标,如果从中间突然指定了一个值,那么它之后的值都会从当前值开始重新计算。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
let d: Direction = Direction.Up;
console.log(d);//UP
let n: null = null;
let u: undefined = undefined;
TypeScript 中的 any 类型表示任意数据类型。
enum Flag {
success,
error,
overtime
};
let flag: any = true;//布尔型
let num: any = 123;//数字型
let str: any = 'Hello,TypeScript';//字符型
let arr: any = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];//数组型
let tuple: any = [10086, 'Nick'];//元组型
let e: any = Flag.success;//枚举型
let n: any = null;//null型
let u: any = undefined;//undefined型
TypeScript 中的 void 类型表示没有任何类型,一般用于定义方法的时候方法没有返回值。
function success(): void {
console.log('执行成功了,我不需要返回值');
}
TypeScript 中的 never 类型是任何类型的子类型,也可以赋值给任何类型,但是没有类型是 never 的子类型或可以赋值给 never 类型, 即使 any 类型也不可以赋值给never。这意味着声明 never 类型的变量只能被 never 类型所赋值。
TypeScript 中支持一个变量可以赋予多种不同的变量类型,多个变量类型使用 |
分隔。
let num: number | null | undefined;
num = 3;
console.log(num);
num = null;
console.log(num);
num = undefined;
console.log(num);
函数是由一连串的子程序(语句的集合)所组成的,可以被外部程序调用,向函数传递参数之后,函数可以返回一定的值。
通常情况下,TypeScript 代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行了声明,其中的代码并不会执行,只有在调用函数时才会执行函数体内部的代码。
函数格式一:
function 函数名(参数列表): 返回值类型 {
函数体 ...
[return 返回值;]
}
函数格式二:
let 函数名 = function (参数列表): 返回值类型 {
函数体 ...
[return 返回值;]
};
必选参数:在调用函数的时候,必须要传入的参数,参数列表里边的参数默认就是必选参数,只要在声明的时候写了参数,在传递的时候,就必须传入参数,而且,实参与形参的数量与类型要一致。
function getInfo(name: string, age: number): string {
return `${name} --- ${age}`;
}
console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 错误
console.log(getInfo(28)); // 错误
必选参数:为了解决在函数传参的时候,某些参数可以不用传递,我们就需要可选参数了。
function getInfo(name: string, age?: number): string {
return `${name} --- ${age}`;
}
console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 正确
console.log(getInfo(28)); // 错误
注意:可选参数必须配置到参数的最后面。
默认参数:为了解决在函数传参的时候,某些参数可以不用传递,但是我们又需要该参数的值,这时候我们就需要给这个参数设定一个默认值也叫初始化值,就得用到默认参数了。
function getInfo(name: string, age: number = 20): string {
return `${name} --- ${age}`;
}
console.log(getInfo("张三", 28)); // 正确
console.log(getInfo("张三")); // 正确
console.log(getInfo(28)); // 错误
注意:可选参数不能够进行初始化值的设定。
剩余参数:在参数的类型确定而参数个数不确定的情况时,我们需要用到剩余参数,它使用 ...
将接收到的参数传到一个指定类型的数组中。
function sum(...result: number[]): number {
let sum = 0;
for (let i = 0; i < result.length; i++) {
sum += result[i];
}
return sum;
}
console.log(sum(1, 2, 3, 4, 5, 6));
function sum(init: number, ...result: number[]): number {
let sum = init;
for (let i = 0; i < result.length; i++) {
sum += result[i];
}
return sum;
}
console.log(sum(100, 1, 2, 3, 4, 5, 6));
注意:剩余参数必须配置到参数的最后面。
3.7、重载函数
重载指的是两个或者两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况。
TypeScript 中的重载是通过为同一个函数提供多个函数类型声明来实现函数重载的功能的。
//重载函数声明
function getInfo(name: string): string;
function getInfo(name: string, age: number): string;
//重载函数签名:就是把声明中出现的参数都写出来,如果可选,就使用可选参数,一个变量名可以使用多种类型用组合类型
function getInfo(name: string, age?: string | number): string {
if (age) {
return "我叫:" + name + ",年龄:" + age;
} else {
return "我叫:" + name;
}
}
console.log(getInfo("zhangsan"));// 正确
console.log(getInfo("lisi", 20));// 正确
console.log(getInfo(123));// 错误
箭头函数其实就是简化了函数当作参数传递时匿名函数的写法,具体可参考ES6新特性。
setTimeout(function () {
console.log("匿名函数执行了...");
}, 1000);
setTimeout(() => {
console.log("箭头函数执行了...");
}, 1000);
class Person {
name: string;//属性,前面省略了public关键词
constructor(n: string) {//构造函数,实例化类的时候触发的方法
this.name = n;//使用this关键字为当前类的name属性赋值
}
run(): void {//方法
console.log(this.name+ "在跑步");
}
}
var p = new Person("张三");
p.run();
类的继承:在 TypeScript 中要想实现继承使用 extends
关键字,只要一旦实现了继承关系,那么子类中便拥有了父类的属性和方法,而在执行方法过程中,首先从子类开始找,如果有,就使用,如果没有,就去父类中找。类的继承只能单向继承。
class Person {
name: string;//父类属性,前面省略了public关键词
constructor(n: string) {//构造函数,实例化父类的时候触发的方法
this.name = n;//使用this关键字为当前类的name属性赋值
}
run(): void {//父类方法
console.log(this.name + "在跑步");
}
}
//中国人这个类继承了人这个类
class Chinese extends Person {
age: number;//子类属性
constructor(n: string, a: number) {//构造函数,实例化子类的时候触发的方法
super(n);//使用super关键字调用父类中的构造方法
this.age = a;//使用this关键字为当前类的age属性赋值
}
speak(): void {//子类方法
super.run();//使用super关键字调用父类中的方法
console.log(this.name + "说中文");
}
}
var c = new Chinese("张三", 28);
c.speak();
TypeScript 里面定义属性的时候给我们提供了 三种修饰符
public:公有类型,在当前类里面、子类、类外面都可以访问
protected:保护类型,在当前类里面、子类里面可以访问,在类外部没法访问
private:私有类型,在当前类里面可以访问,子类、类外部都没法访问
注意:如果属性不加修饰符,默认就是公有(public)。
静态属性:被静态修饰符修饰的属性就是静态属性,静态属性可以通过类名直接调用。
class Person {
name: string;//属性,前面省略了public关键词
static sex: string = "男";//被静态修饰符static修饰的属性
constructor(n: string) {//构造函数,实例化类的时候触发的方法
this.name = n;
}
run(): void {//方法
console.log(this.name+ "在跑步");
}
}
console.log(Person.sex);
静态方法:被静态修饰符修饰的方法就是静态方法,静态方法可以通过类名直接调用,但是在静态方法内部,不能直接调用当前类的非静态属性、非静态方法。
class Person {
name: string;//属性,前面省略了public关键词
static sex: string = "男";//被静态修饰符static修饰的属性
constructor(n: string) {//构造函数,实例化类的时候触发的方法
this.name = n;
}
run(): void {//方法
console.log(this.name + "在跑步");
}
static print(): void {//被静态修饰符static修饰的方法
// console.log('姓名:' + this.name);//错误
console.log('性别:' + Person.sex);//正确
// this.run();//错误
}
}
Person.print();
TypeScript 中的抽象类:它是提供其他类继承的基类,不能直接被实例化。
用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类(也就是其子类)中实现,abstract抽象方法只能放在抽象类里面。
我们常常使用抽象类和抽象方法用来定义标准。
//动物抽象类,所有动物都会跑(假设),但是吃的东西不一样,所以把吃的方法定义成抽象方法
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
abstract eat(): any;//抽象方法不包含具体实现并且必须在派生类中实现
run() {
console.log(this.name + "会跑")
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
eat(): any {//抽象类的子类必须实现抽象类里面的抽象方法
console.log(this.name + "吃骨头");
}
}
var d: Dog = new Dog("小狼狗");
d.eat();
class Cat extends Animal {
constructor(name: string) {
super(name);
}
eat(): any {//抽象类的子类必须实现抽象类里面的抽象方法
console.log(this.name + "吃老鼠");
}
}
var c: Cat = new Cat("小花猫");
c.eat();
多态:父类定义一个方法不去实现,让继承它的子类去实现 ,每一个子类有不同的表现,多态属于继承。
//动物抽象类,所有动物都会跑(假设),但是吃的东西不一样,所以把吃的方法定义成抽象方法
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
abstract eat(): any;//抽象方法不包含具体实现并且必须在派生类中实现
run() {
console.log(this.name + "会跑")
}
}
class Dog extends Animal {
constructor(name: string) {
super(name);
}
eat(): any {//抽象类的子类必须实现抽象类里面的抽象方法
console.log(this.name + "吃骨头");
}
}
var d: Animal = new Dog("小狼狗");
d.eat();
class Cat extends Animal {
constructor(name: string) {
super(name);
}
eat(): any {//抽象类的子类必须实现抽象类里面的抽象方法
console.log(this.name + "吃老鼠");
}
}
var c: Animal = new Cat("小花猫");
c.eat();
在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
5.2、接口的用途
接口的用途就是对行为和动作进行规范和约束,跟抽象类有点像,但是,接口中不能有方法体,只允许有方法定义。
5.3、属性类型接口
//对传入对象的属性约束,以下这个是一个属性接口
interface FullName {
firstName: string;
secondName: string;
}
function printName(name: FullName) {
console.log(name.firstName + "--" + name.secondName);
}
//传入的参数必须包含firstName、secondName
var obj = {
age: 20,
firstName: '张',
secondName: '三'
};
printName(obj);//正确
// printName("1213");//错误
//加密的函数类型接口
interface encrypt {
(key: string, value: string): string;
}
var md5: encrypt = function (key: string, value: string): string {
//模拟操作
return key + "----" + value;
}
console.log(md5("name", "zhangsan"));
var sha1: encrypt = function (key: string, value: string): string {
//模拟操作
return key + "====" + value;
}
console.log(sha1("name", "lisi"));
可索引接口就是对数组、对象的约束,不常用。
//可索引接口,对数组的约束
interface UserArr {
[index: number]: string
}
var arr1: UserArr = ["aaa", "bbb"];
console.log(arr1[0]);
//可索引接口,对对象的约束
interface UserObj {
[index: string]: string
}
var arr2: UserObj = { name: '张三', age: '21' };
console.log(arr2);
类类型接口就是对类的约束,它和抽象类抽象有点相似。
interface Animal {
name: string;
eat(str: string): void;
}
class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat() {
console.log(this.name + "吃大骨头");
}
}
var d = new Dog("小狼狗");
d.eat();
class Cat implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
eat(food: string) {
console.log(this.name + "吃" + food);
}
}
var c = new Cat("小花猫");
c.eat("大老鼠");
接口可以继承接口,接口之间和抽象类之间的继承都是单向单继承,但是实现接口的子类可以实现多个接口。
简单来说,对于类、抽象类、接口继承只能单继承,但接口却可以多实现。
//人这个接口
interface Person {
eat(): void;
}
//程序员接口
interface Programmer extends Person {
code(): void;
}
//小程序接口
interface Web {
app(): void;
}
//前端工程师
class WebProgrammer implements Person, Web {
public name: string;
constructor(name: string) {
this.name = name;
}
eat() {
console.log(this.name + "下班吃饭饭")
}
code() {
console.log(this.name + "上班敲代码");
}
app() {
console.log(this.name + "开发小程序");
}
}
var w = new WebProgrammer("小李");
w.eat();
w.code();
w.app();
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据,这样用户就可以以自己的数据类型来使用组件。
通俗理解:泛型就是解决类、接口、方法的复用性、以及对不特定数据类型的支持。
6.2、泛型类
泛型类可以支持不特定的数据类型,要求传入的参数和返回的参数必须一致,T表示泛型,具体什么类型是调用这个方法的时候决定的。
//类的泛型
class MinClas<T>{
public list: T[] = [];
add(value: T): void {
this.list.push(value);
}
min(): T {
var minNum = this.list[0];
for (var i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum = this.list[i];
}
}
return minNum;
}
}
//实例化类并且制定了类的T代表的类型是number
var m1 = new MinClas<number>();
m1.add(11);
m1.add(3);
m1.add(2);
console.log(m1.min());
//实例化类并且制定了类的T代表的类型是string
var m2 = new MinClas<string>();
m2.add('c');
m2.add('a');
m2.add('v');
console.log(m2.min());
//泛型接口
interface ConfigFn<T> {
(value: T): T;
}
function getData<T>(value: T): T {
return value;
}
var myGetData: ConfigFn<string> = getData;
console.log(myGetData('20'));
//定义操作数据库的泛型类
class MysqlDb<T>{
add(info: T): boolean {
console.log(info);
return true;
}
}
//想给User表增加数据,定义一个User类和数据库进行映射
class User {
username: string | undefined;
pasword: string | undefined;
}
var user = new User();
user.username = "张三";
user.pasword = "123456";
var md1 = new MysqlDb<User>();
md1.add(user);
//想给ArticleCate增加数据,定义一个ArticleCate类和数据库进行映射
class ArticleCate {
title: string | undefined;
desc: string | undefined;
status: number | undefined;
constructor(params: {
title: string | undefined,
desc: string | undefined,
status?: number | undefined
}) {
this.title = params.title;
this.desc = params.desc;
this.status = params.status;
}
}
var article = new ArticleCate({
title: "这是标题",
desc: "这是描述",
status: 1
});
var md2 = new MysqlDb<ArticleCate>();
md2.add(article);
对修饰器的实验支持功能在将来的版本中可能更改。
在 “tsconfig” 或 “jsconfig” 中设置 “experimentalDecorators” 选项以删除此警告。
“experimentalDecorators”: true // 启用对ES7装饰器的实验性支持。
7.1、修饰器的定义
装饰器是一种特殊类型的声明,它能够被附加到类、方法、属性或参数上,可以修改类的行为,通俗的讲装饰器就是一个方法,可以注入到类、方法、属性或参数上来扩展类、方法、属性或参数的功能。常见的装饰器有:类装饰器、方法装饰器、属性装饰器、参数装饰器。
装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参),装饰器是过去几年中JS最大的成就之一,已是ES7的标准特性之一。
类装饰器:普通装饰器(无法传参)
function logClass(params: any) {
console.log(params);//params就是当前类
params.prototype.apiUrl = "我是动态扩展的属性";
params.prototype.run = function () {
console.log("我是动态扩展的方法");
}
}
@logClass
class HttpClient {
}
var http: any = new HttpClient();
console.log(http.apiUrl);
http.run();
类装饰器:装饰器工厂(可传参)
function logClass(params: string) {
return function (target: any) {
console.log(target);//target就是当前类
console.log(params);//params就是当前类传递进来的参数
target.prototype.apiUrl = params;
}
}
@logClass("http://www.baidu.com")
class HttpClient {
}
var http: any = new HttpClient();
console.log(http.apiUrl);
属性装饰器会被应用到属性描述上,可以用来监视、修改或者替换属性的值。
属性装饰器会在运行时传入下列2个参数:
//属性装饰器
function logProperty(params: any) {//params就是当前类传递进来的参数
return function (target: any, attr: any) {
console.log(target);
console.log(attr);
target[attr] = params;
}
}
class HttpClient {
@logProperty("http://www.baidu.com")
public url: any | undefined;
getData() {
console.log(this.url);
}
}
var http = new HttpClient();
http.getData();
方法装饰器会被应用到方法描述上,可以用来监视、修改或者替换方法定义。
方法装饰器会在运行时传入下列3个参数:
function get(params: any) {//params就是当前类传递进来的参数
return function (target: any, methodName: any, desc: any) {
console.log(target);
console.log(methodName);
console.log(desc);
target.apiUrl = params;
target.run = function () {
console.log("run");
}
}
}
class HttpClient {
public url: any | undefined;
constructor() {
}
@get("http://www.baidu.com")
getData() {
console.log(this.url);
}
}
var http: any = new HttpClient();
console.log(http.apiUrl);
http.run();
参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据 ,传入下列3个参数:
function logParams(params: any) {
return function (target: any, methodName: any, paramsIndex: any) {
console.log(target);
console.log(methodName);
console.log(paramsIndex);
target.apiUrl = params;
}
}
class HttpClient {
getData(@logParams("10086") uuid: any) {
console.log(uuid);
}
}
var http: any = new HttpClient();
http.getData(123456);
console.log(http.apiUrl);
装饰器执行顺序:属性 > 方法 > 方法参数 > 类
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
CommonJS => NodeJS、Browserify
AMD => requireJS
CMD => seaJS
模块功能主要由两个命令构成:export 和 import。
model/m1.ts
//方式一:分别暴露
export let school = "华北理工大学";
export function study() {
console.log("我们要学习!");
}
model/m2.ts
//方式二:统一暴露
let school = "华北理工大学";
function findJob() {
console.log("我们要找工作!");
}
export {school, findJob};
model/m3.ts
//方式三:默认暴露
export default {
school: "华北理工大学",
change: function () {
console.log("我们要改变自己!");
}
}
hello.ts
// 引入 m1.js 模块内容
import * as m1 from "./model/m1";
// 引入 m2.js 模块内容
import * as m2 from "./model/m2";
// 引入 m3.js 模块内容
import * as m3 from "./model/m3";
m1.study();
m2.findJob();
m3.default.change();
8.1.6、解构赋值形式
hello.ts
// 引入 m1.js 模块内容
import {school, study} from "./model/m1";
// 引入 m2.js 模块内容
import {school as s, findJob} from "./model/m2";
// 引入 m3.js 模块内容
import {default as m3} from "./model/m3";
console.log(school);
study();
console.log(s);
findJob();
console.log(m3);
m3.change();
注意:针对默认暴露还可以直接 import m3 from "./model/m3"
8.2、命名空间
命名空间:在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内,同Java的包、.Net的命名空间一样,TypeScript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象,命名空间内的对象通过export关键字对外暴露。
命名空间和模块的区别:
命名空间:内部模块,主要用于组织代码,避免命名冲突。
模块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间。namespace A {
interface Animal {
name: string;
eat(): void;
}
export class Dog implements Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
eat(): void {
console.log(`${this.name} 吃狗粮。`);
}
}
export class Cat implements Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
eat(): void {
console.log(`${this.name} 吃猫粮。`);
}
}
}
namespace B {
interface Animal {
name: string;
eat(): void;
}
export class Dog implements Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
eat(): void {
console.log(`${this.name} 吃狗粮。`);
}
}
export class Cat implements Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
eat(): void {
console.log(`${this.name} 吃猫粮。`);
}
}
}
var ac = new A.Cat("小花");
ac.eat();
var bc = new B.Cat("小花");
bc.eat();