TypeScript从入门到放弃

关注我们

关注【郑大钱呀】【】【】【】,我们一起交流,一起学习。

TypeScript准备工作

什么是TypeScript

TypeScript(下文简称为ts)是微软开发的一个开源的编程语言,通过在JavaScript(下文简称为js)的基础上添加静态类型定义构建而成。ts通过其编译器或Babel转译为js代码,可运行在任何浏览器,任何操作系统。ts是js的超集,它支持所有js的语法,我们可以在ts中使用原生js语法,可以简单的理解成TS是JS的升级版。

ts与js两者的主要的区别如下:

TypeScriptJavaScript
强类型,需要强制转换类型js是弱类型,没有静态类型选项,会自己根据环境变化自动转换类型
ts最终需要编程成js才能在浏览器中运行js可以直接在浏览器中运行
ts是静态类型语言在编译期间就可以发现并纠正错误js是动态类型语言在运行时才知道错误
支持模块、泛型和接口不支持模块、泛型和接口

TypeScript学习环境

学习一门编程语言,首先有其编程环境,为了能方便快速的学习TypeScript语言,推荐一个在线的TypeScript的演练场,打开浏览器,输入网址即可使用,如下:
https://www.typescriptlang.org/zh/play,界面如下:

TypeScript 安装

上面那种方式适合学习,但是工作时总不能这样来写代码,这里我们简单的说一下ts的环境搭建,首先我们需要先安装Node.js,我相信你已经会了,不会就百度嘛

然后我们打开命令行,执行如下命令:

npm install -g typescript

安装完成后,使用如下命令查看是否安装成功,如下:

tsc -v

然后我们打开vscode,创建一个ts文件(名为StudyTS.ts),写下第一条ts代码,如下:

console.log("我要关注郑大钱嗷,关注他,关注他");

然后我们在使用命令行,编译它,将ts,转化成js,命令如下:

tsc StudyTS.ts

当你执行完这条命令的时候,就会理所当然的发现你会多了一个js文件,此时我们来执行这个js文件,命令如下:

node StudyTS.js

执行结果如下:

好了,到这里准备工作就完成了,下面正式开始发车

注释

常言道,兵马未动,粮草先行,我们首先说说注释,注释是不参与代码运行的,是给人看的,意义就是能够让代码更加的易读,更方便的理解代码,所以我们尽可能的养成写注释的好习惯。注释主要分为两种:单行注释和多行注释,示例代码如下:

// 这是单行注释

/**
 * 这是多行注释
 */

变量

变量的命名规范

  1. 变量名称只能有字母、数字、下划线组成,且不要为TypeScript 保留关键字
  2. 当变量名称由多个单词组成的时候,可以使用驼峰命名发,或者下划线风格,如:personName,person_name,都可以,最好不要写成personname,易读性差。

变量浅尝

// 声明一个变量
let perName:string = "郑大钱嗷"
// 声明一个变量,不为其初始化
let sex:string;
// 声明一个常量
const age:number =10
console.log(perName);
console.log(age);

这里我们可以和编译后的js语法对比一下,如下:

"use strict";
// 声明一个变量
let perName = "郑大钱嗷";
// 声明一个变量,不为其初始化
let sex;
// 声明一个常量
const age = 10;
console.log(perName);
console.log(age);

可以发现我们在ts的代码中变量名称多了一个:和变量类型,这个就是限定变量的类型的,如果你想这个变量不需要类型显示,你可以使用js的语法,或者这样写

let perName:any = "郑大钱嗷"

此外假如你给一个变量限定类型为字符串,那么你赋一个整型的值就会报错,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRqSu1JR-1651992033169)(https://files.mdnice.com/user/18450/c2bceda2-7dec-420f-8038-06e0ad75ad0e.png)]
再者,如果我们声明了一个变量,但是没有初始化,如果没有使用该变量,则不会报错,但是如果使用的话就会报错。

变量的联合类型

我们上面限制的是一个类型,就是一个变量只能保存一个类型,我们也可以一个变量保存多个类型,这样这个变量就能保存多个类型的数据,示例代码如下:

let varPro:string|boolean =true;
console.log(varPro);
varPro="郑大钱嗷";
console.log(varPro);

运行结果如下:

数组

数组就是一组数据,主要用来保存类型相同,含义相同的数据,比如我们要定义一个数组用来保存数字,示例代码如下:

let num:number[] = [1,2,3,4,5,6]
console.log(`数字:${num}`);

运行结果如下:

上面我们是打印了所有的数组数据,假如我想取其中某个数据,这个时候需要指定索引,值得注意的是,索引是从0开始的,示例代码如下:

let num:number[] = [1,2,3,4,5,6]
console.log(`数字:${num[0]}`);

运行结果如下:

数组除了以上的声明方式之外,还可以使用Array类来声明,并且可以结合泛型来使用,具体在泛型类讲。

下面我们接着说数组的常用操作方法,直接看示例代码:

let array:number[] = [1,9,3,4,5,6];
console.log(`原始数组:${array}`);

// 在数组的最后位置,添加元素
array.push(7);
console.log(`数组的最后位置,添加元素${array}`);

// 在数组的最前面,添加元素
array.unshift(0);
console.log(`在数组的最前面,添加元素:${array}`);

//删除最后面的元素
array.pop();
console.log(`删除最后面的元素:${array}`);

//删除最前面的元素
array.shift();
console.log(`删除最前面的元素:${array}`);

//从第几位开始删除几个元素,注意索引从0开始,splice的用法不仅是删除,更新数组的内容更贴切,有其他很多用法,可以自行百度
array.splice(0,1);
console.log(`删除指定元素:${array}`);

// 合并两个数组
let array2:number[] =[10,11,12];
array = array.concat(array2);
console.log(`合并两个数组:${array}`);

// 查找元素的位置
console.log(`查找元素的位置:${array.indexOf(10)}`);

// 对数组进行正序排序
console.log(`对数组进行正序排序${array.sort()}`);

// 对数组进行倒序序排序
console.log(`对数组进行正序排序${array.reverse()}`);

运行结果如下:

元组类型

// 声明一个元组
let person:[string,string,number] =['郑大钱嗷','男',18];
// 输出
console.log(person);
console.log(person[0]);

运行结果如下:

元组和数组的区别在于元组知道每个元素的类型,简单的说元组就是在数组的基础上对各个元素加了类型的限制。

字典类型

字典类型由键值对组成,键和值,示例代码如下:

// 声明一个字典,并赋值
let person:{[key:string ]:string }={
    "name":"郑大钱嗷",
    "sex":"男"
}
// 获取name的值
console.log(person["name"]);
// 修改name的值
person["name"]="郑大钱嗷~";
console.log(person["name"]);

运行结果如下:

枚举类型

当一个变量有多种值,然后值又是比较固定的,比如颜色,通常使用的主要颜色就那么几种,这个时候就可以将其定义成一个枚举类型,示例代码如下:

enum Color{
    red,
    orange,
    yellow,
    green
}
console.log(Color.orange);

它最后会返回指定枚举的索引,示例代码如下:

模板字符串

模板字符串相当于定义一个模板,然后在模板里刨坑,然后再把值放在里面,这样就可以按照我们的要求显示字符串了,比直接使用+号拼接更加的方便。示例代码如下:

let perName:string = "郑大钱嗷"
console.log(`名字:${perName}`);

typeof 类型验证

这个typeof的功能就是返回当前变量的数据类型,示例代码如下:

let age = 18;
console.log(typeof age);

运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LBrZJcc2-1651992033183)(https://files.mdnice.com/user/18450/0efc4337-8555-45b8-b1eb-5d516937ee82.png)]

类型别名

这个用的不多,基本不用,就是你觉得系统的number类型这个名字不好听,觉得原来的名字太委屈

这个时候你就可以给他换个名字,示例代码如下:

type haoTingNum = number;
let age:haoTingNum=18;
console.log(age);

运行结果如下:

运算符

算数运算符

算数运算符就是加减乘除、取余,自增和自减示例代码如下:

let a:number = 5;
let b:number =6;
console.log(`a+b=${a+b}`);
console.log(`a-b=${a-b}`);
console.log(`a*b=${a*b}`);
console.log(`a/b=${a/b}`);
console.log(`a%b=${a%b}`);

运行结果如下:

自增和自减运算符:所谓自增/减,就是在之前值的基础上自己加1或减1,自增/减,又分为前自增/减,和后自增/减,示例代码如下:

let a:number = 5;
// 先使用,后自增,此时使用的时候a还是5,使用后a的值为6
console.log(`a++:${a++}`);
// 同样是先使用,后自减,此时使用的是a是6,使用后a的值是5
console.log(`a--:${a--}`);
// 这里不一样了,是先自增,在使用,此时使用时a为6
console.log(`++a:${++a}`);
// 这里是先自减,在使用,此时使用时a为6-1=5
console.log(`--a:${--a}`);

运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VL4oWsuR-1651992033187)(https://files.mdnice.com/user/18450/34f37cde-0825-4e15-9a10-7194a565998c.png)]

比较运算符

所谓比较运算符就是指大于、小于、等于(=)、不等于、大于等于、小于等于,比较运算符返回结果一个布尔值即ture或者false,示例代码如下:

let a:number=1;
let b:number=2;
let c:number=1;
console.log(`a>b: ${a>b}`);
console.log(`a<b: ${a<b}`);
console.log(`a>=b: ${a>=b}`);
console.log(`a<=b: ${a<=b}`);
console.log(`a==b: ${a==b}`);
console.log(`a===b: ${a===b}`);
console.log(`a!=b: ${a!=b}`);

运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LUjhgltV-1651992033188)(https://files.mdnice.com/user/18450/66285d46-275c-4027-a73c-29e8da7da67a.png)]
这里补充一下=====的区别,如果是==比较的是值相等,而===不仅要求值相等,还要求数据类型相等。

逻辑运算符

逻辑运算符,主要有三种,与、或、非,三种,我们举个例子,比如我们想吃冷饮,但是我们有要求,我们必须要吃哈根达斯而且至少还得三个球,必须满足这两个条件,否则不吃。
示例代码如下:

let iceName:string="哈根达斯";
let qiuNum:number=5;
if(iceName=="哈根达斯"&&qiuNum>=3){
    console.log('好好吃')
}else{
    console.log('少球了,哼,不吃');
}

赋值运算符

赋值运算符是一种简写,具体有如下几种:=、+=、-+、*=、/=、%=,这里我们以加法举例,示例代码如下:

let a:number = 1;
a+=1;
let b:number = 1;
b=b+1
console.log(`a:${a}`);
console.log(`b:${b}`);

运行结果如下:


也就是说a+=1就等同于a=a+1

条件语句

判断语句

所谓判断,即使如果这样,我会怎么样,如果那样,又会怎么样,比如上面得那个例子,让我偷下懒,直接拿过来用

let iceName:string="哈根达斯";
let qiuNum:number=5;
if(iceName=="哈根达斯"&&qiuNum>=3){
    console.log('好好吃')
}else{
    console.log('少球了,哼,不吃');
}

判断语法主要有如下几种结构,if...if...else...if...elseif...else...,我们来看一个经典的案例,根据分数来判断成绩的等级,示例代码如下:

let score:number =60;
if(score>=90 && score<=100){
    console.log("优秀");
}else if(score>=80 && score<90){
    console.log("良好");
}else if(score>=70 && score<80){
    console.log("中等");
}else if(score>=60 && score<70){
    console.log("及格");
}else{
    console.log("支棱起来,好嘛");
}

运行结果如下:

此外有个和判断语句类似的运算符,叫三目运算符,写起来更加的简单,具体示例如下:

//三目运算符 条件?值1:值2
let sex:string ="男";
console.log(sex=="男"?"好帅啊":"好美啊")

运行结果如下:

当我们有多种条件判断的时候,if判断语句不是特别的好用,我们可以使用switch语句,示例代码如下:

enum Color{
    red,
    black,
    green
}

let color:Color = Color.red
switch(color){
    case Color.red :
        console.log("red");
        break;
    case Color.black :
        console.log("black");
        break;
    case Color.green:
        console.log("green");
        break;
    default:
        console.log("其他颜色");
}

运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cNwSfDo5-1651992033195)(https://files.mdnice.com/user/18450/19845c0f-8946-429a-a6e4-a066be6235bc.png)]
这里要值得注意的是,每个条件分支要加break语句,否则匹配之后,还会继续匹配,这里的default就相当与if中的else,即所有分支都没有匹配到才会执行。

循环语句

所谓循环就是反复的执行某些事情,比如说,让唐僧念100遍经
我们肯定不可能这样写:

console.log("念经");
console.log("念经");
console.log("念经");
console.log("念经");
console.log("念经");
console.log("念经");
console.log("念经");
//...100遍

这样也可以实现,但是有点不太聪明的样子,于是我们需要更高级的魔法来实现,示例代码如下:

let i:number=1;
// 先判断已经念了多少次了,有没有超过100次,没有超过就接着念,超过就不念了
while(i<=100){
    console.log(`唐僧念经,第${i}次`);
    i++;
}

上面是while...do..的写法,即先判断,在执行,还有一种do...while...的写法,即先执行再判断,代码如下:

let i:number=1;

do{
    console.log(`唐僧念经,第${i}次`);
    i++;
}while(i<=100);

值得注意的一点是,循环的条件一定要有出口,即满足什么条件后,就不执行了,否则就会一直执行,就是死循环.

循环也可以遍历数据,示例代码如下:

let names:string[]=["郑","争","争"];
let i:number=0;
while(i<names.length){
    console.log(names[i]);
    i++;
}

运行结果如下:

除了有while循环外,还有for循环,示例代码如下:

let names:string[]=["郑","争","争"];
for(let j=0;j<names.length;j++){
    console.log(names[j]);
}

对于数组,我们for循环还有另一种写法,更方便,更加简单,示例代码如下:

let names:string[]=["郑","争","争"];
//是用of,是把值给name,如果是in,则是把索引给name
for(let name of names){
    console.log(name);
}

函数

函数就是一段代码块,目的是为了能够更好的实现代码的复用,但我们定义了一个函数后,当我们需要使用的时候,就可以直接调用,代码的复用性大大提高,代码量会大大的减少

让我们先简单的感受一下:

// 定义一个有参数的函数
function saySomething(word:string):void{
    console.log(word);
}
// 调用函数
saySomething("你好");
saySomething("我很好,非常好,老好了");

运行结果如下:


我们上面定义了一个函数,function 为定义函数的关键字,函数名为saySomething,里面的word,为传入函数的参数,我们一般成为入参,同样有入参肯定就有出参,这里的void表示没有返回值,即没有出参,我们这个函数没有定义出参,下面我们定义一个加法的方法来返回一个出参,示例代码如下:

function add(num1:number,num2:number){
    return num1+num2;// 返回计算结果
}
console.log(add(1,3));

运行结果如下:

上面的函数,还可以有如下2种写法,示例代码如下:

let add = function(num1:number,num2:number){
    return num1+num2;
}
console.log(add(1,3));

let add2 =(num1:number,num2:number) =>{
    return num1+num2;
}
console.log(add(1,3));
console.log(add2(1,3));

面向对象

什么是对象?在我们在说对象之前,我们了解一下类,我们把一切具有共性的事务可以定义成一个类,类就相当于一个模板,比如狗、猫、猪,都可以当成一个类,那什么是对象呢,对象就是对类的具体实现,比如猪是一个类,但是猪有好多种,有苏格兰打卤猪、黑猪、野猪、母猪等等,我们可以一句话总结一下:类是对象的抽象,而对象是类的具体化
我们先来感受一下,示例代码如下:

// 定义一个猪类
class Pig{
    //定义类的熟悉
    name:string="猪";
    age:number = 1;
    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
    }
    eat():void{
        console.log("吃东西")
    }
}

//实例化对象
let pig1 = new Pig("飞猪",10);
pig1.eat();

我们受限创建了一个Pig的类型,里面定义了两个属性nameage,下面的constructor为构造方法,所谓构造方法就是当你对对象实例化的时候,就执行的方法,如果构造方法需要传参的时候,那么再实例化对象的时候就需要为其传入参数,否则会报错,下面的eat方法就是普通的方法。

我们实例化的时候,需要使用 new关键字来实例化,实例化完成后,我们就可以使用对象了。

静态(成员)属性和静态(成员)方法

静态属性、方法就是有static关键字修饰的,不需要实例化对象,使用类名就可以调用,而成员属性、方法是属于对象的,需要实例化后才能调用,实例代码如下:

// 定义一个猪类
class Pig{
    //定义类的熟悉
    name:string="猪";
    age:number = 1;
    static belong:string="动物"

    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
    }
    eat():void{
        console.log("吃东西")
    }
    static sleep():void{
        console.log("睡觉觉")
    }
}

//实例化对象
let pig1 = new Pig("飞猪",10);
//成员方法调用
pig1.eat();
console.log(pig1.name);

//静态方法调用
Pig.sleep();
console.log(Pig.belong);

运行结果如下:

继承

所谓继承就是子类继承父类,就与儿子继承父亲的财产是一样,比如我们有一个父类为车类,然后我们有一个子类为面包车,为了方便代码的复用以及可拓展性,我们可以先定义父类,然后用子类继承父类,这样子类就不需要重新编写了,只需要再父类的基础上就行修改就可以了,示例代码如下:

// 定义一个Car类
class Car{
    color:string = "black";
    seats:number= 5;

    startCar():void{
        console.log("启动汽车")
    }
}
// 子类继承父类
class MianBaoCar extends Car{

}

let car1 = new MianBaoCar();
car1.startCar();

我们定义了一个父类Car,然后子类MianBaoCar继承父类,这样子类就可以调用父类的方法了,但是如果父类的方法不适合子类的方法,我们想要覆盖父类的方法,应该怎么做呢,我们只需要在子类种定义一个方法与父类中的方法同名,就可以覆盖父类的方法,示例代码如下:

// 定义一个Car类
class Car{
    color:string = "black";
    seats:number= 5;

    startCar():void{
        console.log("启动汽车");
    }
}

class MianBaoCar extends Car{
    startCar():void{
        console.log("启动面包车");
    }

}

let car1 = new MianBaoCar();
car1.startCar();

运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rd8a75kl-1651992033203)(https://files.mdnice.com/user/18450/efefe9f6-2bf4-4938-aafe-4717c1fb1924.png)]

但是此时你想,我不要完全覆盖,我想要在父类之前的方法上拓展,怎么做呢,我们可以使用super关键字,来调用父类的方法后再拓展,示例代码如下:

// 定义一个Car类
class Car{
    color:string = "black";
    seats:number= 5;

    startCar():void{
        console.log("启动汽车");
    }
}

class MianBaoCar extends Car{
    startCar():void{
        super.startCar();
        console.log("呜呜呜~~~~");
    }

}

let car1 = new MianBaoCar();
car1.startCar();

运行结果如下:

抽象类

所谓抽象类,就是抽取子类共有的部分,仅仅做定义,不做具体的实现,具体的实现需要在子类中实现,且子类必须实现,我们看一个简单的示例:

// 定义一个抽象类
abstract class Person{
    abstract name:string;
    abstract say():void;
}

class Student extends Person{
    name="郑大钱嗷"
    say(){
        console.log("说话")
    }
}

let stu:Person = new Student();
stu.say();

运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vb45Nph9-1651992033205)(https://files.mdnice.com/user/18450/83e654f1-fe4e-457c-a20a-f5c4b07f6ba3.png)]

接口

由于ts只能进行单继承,没有办法实现多继承,接口就是为了弥补ts没有多继承的特性,同时接口也是相当于一个规范,当子类使用一个接口的时候,需要实现接口里的所有方法,示例代码如下:

class Person{
    name:string ='人'
}

interface Wolf{
    bite():void;//只能定义,不能实现
}

class WolfMan extends Person implements Wolf{
    bite(){
        console.log("撕咬~")
    }
}

let a = new WolfMan();
a.bite();

运行结果如下:

私有属性及其操作(属性寄存器)

如果有接触过java的同学,就会发现这个很熟悉,就是setter和getter方法,即对属性取值和赋值的时候,只能通过setter/getter方法,这样可以很好的保护属性的值,下面我们看一个示例:

class Person{
    _hp:number = 100; //使用的名字的话 前面必须要加上_
    // 取值
    set hp(value){
        // 如果hp小于0,直接赋值为0,这样就不会出现负数了
        if (value<0){
            this._hp=0;
        }else{
            this._hp=value;
        }
        
    }

    // 赋值
    get hp():number{
        return this._hp;
    }

}

let a = new Person();
a.hp=-10;
console.log(a.hp)

运行结果如下:

命名空间

命名空间的出现主要是为了区分代码中定义的标识符,通过 namespace 关键字声明命名空间,在命名空间外部需要通过完全限定名访问这些对象,这样即便有同名的类名、变量,也不会有冲突,示例代码如下:

// 声明命名空间
namespace space1{
    // 需要导出,否则会有报错
    export class Person{

    }
}

namespace space2{
    export class Person{

    }
}
// 实例化的时候,需要使用命名空间.类名的形式
let a = new space1.Person();

泛型

所谓泛型根据字面意思了解,就是宽泛的类型,通常用于类和函数,用于对不特定数据类型的支持,也就是输入的参数类型是可变的,根据你的输入参数类型来决定,我们先看一段示例代码

function addT<T>(num:T):T{
    if (typeof num=="number"){
        num++;
        return num;
    }
    return num;
}
console.log(addT(3));

运行结果


上面的的<T>表示的就是一个泛型,出参和入参都是可变的,且两者的类型一致,比如你输入的是一个number数据类型,那么上面的函数一定返回number类型。

在数组的声明中,我们也可以使用泛型,示例代码如下:

// 使用泛型声明一个空的数组
let array: Array<number> = new Array<number>();
console.log(array);

运行结果如下:

回调

我们之前在定义函数的时候,我们的入参一般是一个值,但是现在我们传入的不是一个值,而是一个函数体。

function func(value:Function){
    value();

}
function add(){
    console.log("测试方法");
}
func(add)

运行结果如下:


除了上面的写法外,我们还有另一种写法,匿名函数,示例代码如下:

function func(value:Function){
    value();

}

func(function add(){
    console.log("测试方法");
})

我们也可以使用箭头函数的方式,示例代码如下:

function func(value:Function){
    value();

}

func(()=>{
    console.log("测试方法");
});

上面的函数是没有传入参数的,我们也可以传入参数,示例代码如下:

function func(value:Function){
    value("测试");

}

func((str:string)=>{
    console.log(str);
});

正则表达式

正则表达式就是用来匹配一系列符合某些语法规则的字符串,正则表达式通常被用来检索、替换符合某个模式的文本。正则表达式是由普通字符、元字符、限定符组成,具体的这里就不展开说了,主要看一下,ts中正则表达式的使用,示例代码如下:

let reg =/\d{2}-\d{4}/g;
let str:string="1111-12345678";
// 输出匹配结果
let res = reg.exec(str);
console.log(`匹配的次数:${res.length}`);
// 输出匹配的内容
res.forEach(function(value,index){
    console.log(`value:${value},index:${index}`);
});

运行的结果如下:

访问修饰符

public 修饰符

示例代码如下:

class Person{
    // public 公开的,属性默认为public,表示属性和方法,内部和外部都可以访问
    public name: string|undefined;
    public say(){

    }
}

class Student extends Person{
    constructor(){
        super();
    }
}


let a:Person = new Person();
a.name="";
a.say();

public表示 公开的,属性默认为public,表示属性和方法,内部、外部、子类都可以访问

protected 修饰符

示例代码如下:

class Person{
    protected name: string|undefined;
    protected say(){

    }
}

class Student extends Person{
    constructor(){
        super();
    }
}

protected修饰符表示受保护的,在内部和子类都可以访问,但是外部无法访问。

private 私有的

示例代码

class Person{
    private name: string|undefined;
    private say(){

    }
}

class Student extends Person{
    constructor(){
        super();
    }
}

protected修饰符表示私有的,只能在内部访问,但是子类、外部无法访问。

单例模式

单例模式,就是通过单例模式的方法创建的类在当前进程中只有一个实例,示例代码如下:

class Manager{
    
    // 内部创建自己的对象,并用静态修饰
    static Instance = new Manager();
    
    // 构造方法,私有化,不能让外部new对象
    private constructor(){

    }

}

console.log(Manager.Instance);

我们还有第二种写法,示例代码如下:

class Manager{
    
    // 内部创建自己的对象,并用静态修饰
    private static instance:Manager;
    
    // 构造方法,私有化,不能让外部new对象
    private constructor(){

    }
    static Instance(){
        // 如果没有单例产生就创建单例
        if(!Manager.instance){
            Manager.instance = new Manager();
        }
        return Manager.instance;
    }

}

Manager.Instance();

代理模式

为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。简单的说我要做一件事,但是我不做,我去找一个人(即:代理人)给我做,然后给我返回结果。

// 定义一个接口
interface Calc{
    calc(a:number,b:number):number;
}
// 代理人1,实现接口,要想做代理人,必须要实现计算的方法
class Agent1 implements Calc{
    calc(a:number,b:number){
        return a+b;
    }
}

// 代理人2
class Agent2 implements Calc{
    calc(a:number,b:number){
        return a+b;
    }
}


class Person{

    currAgent: Calc;

    getNum(a:number,b:number){
        console.log(this.currAgent.calc(a,b));
        
    }


}


let person = new Person();
person.currAgent = new Agent1();
person.getNum(1,3);

运行结果如下:

观察者模式

当对象间存在一对多关系时,则使用观察者模式。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。示例代码如下:

interface Listener{
    changeName(name:string):void;
}

class Person {
    private _perName:string="";

    // 所有观察者
    obListeners:Array<Listener> = new  Array<Listener>();


    set perName(value:string){
        this._perName=value;
        for(let listener of this.obListeners){
            listener.changeName(this._perName);
        }
    }
    get perName():string{
        return this._perName
    }
}

class Test implements Listener{
    changeName(newName:string){
        console.log("名字有变化!,新名称:"+newName);
    }
}

let per = new Person();
let listener1 = new Test();
let listener2 = new Test();
per.obListeners.push(listener1);
per.obListeners.push(listener2);
per.perName="郑大钱嗷"

运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FzBAHqNi-1651992033213)(https://files.mdnice.com/user/18450/747038e3-e0f7-445a-81b0-9cf98b9827d8.png)]

工厂模式

定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行,在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。示例代码如下:

enum CarType{
    BenZ,
    Audi,
    Bmw
}

class Car{
    name:string="";
    // 工厂方法
    static createCar(carType:CarType):Car{
        let car:Car;
        switch(carType){
            case CarType.Audi:
                car = new Audi();
                break;
            case CarType.BenZ:
                car = new Benz();
                break;
            case CarType.Bmw:
                car = new Bmw();
                break;
        }
        return car;

    }
}

class Benz extends Car{}
class Audi extends Car{}
class Bmw extends Car{}

Car.createCar(CarType.Audi);

版权声明:本文为MicalChen原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。