使用TypeScript就是为了规范开发环境,但是在真正的运行环境里,TypeScript实际上并不起任何约束作用。
一、安装
1.安装TypeScript
npm install typescript -g
安装完成后控制台输入tsc命令检查是否安装成功,如果出来一大堆英文,就代表你安装好了
现在我们可以在自己的电脑上进行typescript的编写了,但是如果我们想要运行一个typescript文件,我们需要先使用tsc命令将.ts文件编译成.js文件,然后才能运行其中的代码。
tsc 文件名.ts
node 文件名.js
这样对我们开发时非常低效的,不过我们可以使用ts-node直接执行typescript文件,调过中间的js转换过程。
2.安装ts-node
npm install -g ts-node
此后我们就可以使用ts-node命令直接执行ts文件了
ts-node 文件名.ts
二、类型注释
我们都知道,JavaScript是一种弱类型语言,弱类型语言对于我们规范开发过程是不利的,类型注释就是TypeScript提出的一种强化语言类型的方案,因此,TypeScript也是一种强类型语言。
比如我们定义了一个变量age是number类型的,那么我们就不能给它附一个其他类型的值。
let age: number;
age = 123;
如上面的例子所示,typescript中对类型的注释就是使用 ":" 关键字,: + 数据类型 即可完成声明
数据类型 | 关键词 |
---|---|
String | string |
Number | number |
Boolean | boolean |
Void | void |
Any | any |
Undefined | undefined |
Null | null |
1.数组的类型注释
数组中类型统一
const arr: number[] = [1, 2, 3];
数组中类型不统一
const arr: (number | string)[] = [1, "string", 2];
对象数组
const student: { name: string, age: number }[] = [
{ name: "小白", age: 21 },
{ name: "小黑", age: 18 },
];
2.元组的使用
数组的不足
当我们在使用数组处理一个业务时,如果数组中元素的顺序发生了改变,那么我们的业务逻辑就会出现错误,但是数组的类型注释却不会报错,此时我们就要使用元组的类型注释(类型约束)。
// 如果数组中的元素顺序发生变化,数组的类型注释不报错,存在开发隐患
const beauty1: (string | number)[] = ["A", "student", 18]
const beauty2: (string | number)[] = ["A", 18, "student"]
// 使用元组中的类型约束可以解决此隐患
const beauties1: [string, string, number] = ["A", "student", 18]
const beauties2: [string, string, number] = ["A", 18, "student"] //报错!
image.png
三、Interface的使用
1.接口的基础应用
对于接口的简单应用,我们直接上例子吧,比如我们以学生举一个例子。
// 创建接口
interface Student {
name: string;
age: number;
exam?: number; //非必须属性在“:”前加上“?”即可
[propname: string]: any; //此接口允许添加新的属性
}
// 接口应用实例
const getStudent = (student: Student) => {
console.log("名字:" + student.name)
console.log("年龄:" + student.age)
// 做一个小判断,如果有成绩则输出成绩,没有成绩则输出提示
student.exam ? console.log("成绩" + student.exam) : console.log("此人无成绩")
}
// 数据
const xiaobai = {name: "小白", age: 21, exam: 90}
const xiaohei = {name: "小黑", age: 18}
// 以传值的形式调用方法
getStudent(xiaobai)
getStudent(xiaohei)
image.png
2.接口中的方法
基于上面的例子,如果我们想给Student接口中存一个方法work(),那么我们直接在接口中声明方法并约束其返回值类型即可。
interface Student {
name: string;
age: number;
exam?: number; //非必须属性在“:”前加上“?”即可
[propname: string]: any; //此接口允许添加新的属性
work(): string; //声明方法的返回值类型为string
}
在接口中声明完方法,接下来我们在实际对象中完善该方法,以便后面的使用。
const xiaobai = {
name: "小白", age: 21, exam: 90,
work() {
return "学习就是我的工作"
}
}
const getStudent = (student: Student) => {
console.log("名字:" + student.name)
console.log("年龄:" + student.age)
// 做一个小判断,如果有成绩则输出成绩,没有成绩则输出提示
student.exam ? console.log("成绩" + student.exam) : console.log("此人无成绩")
console.log(student.work())
}
3.接口中类对接口的实现
我们在实现类的时候记得要把class写的全一点,否则会报错
class xinsheng implements Student {
name = "小白";
age = 21;
work() {
return "学习就是我的工作"
}
}
4.接口之间的继承
Student接口可以作为一个基础,如果我们需要一个新的基于Student接口的方法,比如班长Monitor接口,其中Monitor中有一个特殊的方法收作业shouzuoye,那么我们就可以这么实现:
interface Monitor extends Student {
shouzuoye(): string;
}
具体实例:
interface Student {
name: string;
age: number;
exam?: number; //非必须属性在“:”前加上“?”即可
[propname: string]: any; //此接口允许添加新的属性
work(): string; //声明方法的返回值类型为string
}
// 接口的继承
interface Monitor extends Student {
shouzuoye(): string;
}
// 接口应用实例
const monitor = (teacher: Monitor) => {
console.log("名字:" + teacher.name)
console.log("年龄:" + teacher.age)
// 做一个小判断,如果有成绩则输出成绩,没有成绩则输出提示
teacher.exam ? console.log("成绩" + teacher.exam) : console.log("此人无成绩")
console.log(teacher.work())
console.log(teacher.shouzuoye())
}
const xiaohei = {
name: "小黑", age: 18,
work() {
return "学习就是我的工作"
},
shouzuoye(){
return "大家把作业交给我"
}
}
// 以传值的形式调用方法
monitor(xiaohei)
image.png
四、类的使用
typescript中类的概念和java以及es6中的基本一样,没什么区别,上例子。
1.类的定义及使用
class Person {
name = "小黑";
age = 18;
say() {
return "我叫" + this.name
}
}
const p = new Person();
console.log(p.say());
2.类的继承
子类继承父类的所有属性及方法,并且可以直接调用。
class Student extends Person{
work(){
return "淦!我是学生,我得学习。"
}
}
const S = new Student();
console.log(S.say())
console.log(S.work())
image.png
3.类的重写以及super关键字的使用
子类可以重写父类中的方法,写法就是把父类中的方法重新写一遍,其中如果我们需要调用父类中的属性,那么我们可以通过super关键字进行调用。
class Student extends Person{
work(){
return "淦!我是学生,我得学习。"
}
// 重写父类中的say()方法
say(){
return super.say() + "很高兴认识你!"
}
}
const S = new Student();
console.log(S.say())
console.log(S.work())
image.png
4.类的访问类型以及只读属性
类的访问类型有public、private、protected
public
typescript默认的访问类型,如果你在写代码的时候没有声明访问类型,那么typescript会默认为public类型。
public访问类型在类的内部外部都能使用,例如我们在类的内部声明了一个变量,我们在类的外部就可以直接调用该变量,也可以修改此变量
class Person{
public name:string;
public say(){
console.log(this.name)
}
}
const p = new Person();
p.say() //第一次
p.name = "小白"
p.say() //第二次
两次的输出结果表明类的内部变量被外部操作修改了。
image.png
private
私有类型,和上面的public正好相反,可以这么理解,只要出了class的大括号,没有任何操作能直接调用他(注意这里是直接调用,后面我们会有操作私有类型的方法),这里就不给大家演示例子了。
protected
保护类型,此类型和private差不多,都是出了大括号就不能直接操作,但是protected允许在继承时被操作
class Person{
protected name:string;
public say(){
console.log(this.name)
}
}
class Student extends Person{
name = "小黑";
sayHello(){
console.log(this.name)
}
}
const p = new Student();
p.say()
p.sayHello()
image.png
只读属性readonly
在类中声明变量时可以定义变量的类型,其中有一个属性叫readonly只读属性,加上此属性的变量在尝试改变其值的时候会报错。
class Person{
public readonly name: string;
}
5.类的构造函数
普通的构造函数
typescript的类中有一个constructor方法,也就是我们的构造函数,我们可以使用这个方法对类进行初始化,而且typescript中的constructor写起来很方便,具体如下:
class Person {
constructor(private name: string, public age: number) { }
}
const p = new Person("小黑", 21)
// console.log(p.name); //这里记得name是私有变量,外部不能直接调用
console.log(p.age)
子类的构造函数
子类的构造函数比较特殊,由于进行了一次继承extends操作,所以我们在子类中写构造函数的时候必须使用super(),否则typescript会报错。
class Student extends Person {
constructor(private exam: number) {
super("小白",18);
}
}
6.类的setter和getter
在开发过程中,为了保证数据的安全性我们往往会把数据定义private,如果想要调用就会用到setter()方法与getter()方法或者构造函数方法。
class Person {
constructor(private name: string, private age: number) { }
get getName() {
return this.name
}
set setName(name: string) {
this.name = name
}
get getAge() {
return this.age
}
set setAge(age: number) {
this.age = age
}
}
const p = new Person("小黑", 21)
p.setName = "小白"
console.log(p.getName)
image.png
7.抽象类的使用
抽象类abstract的引入能便于我们规范类的编写,上例子:
比如我们进出学校都需要报备自己是那个单位(班级)的。
abstract class School{
// 抽象类中的方法都需要加上abstract关键字,表示抽象方法
abstract baobei()
}
class Student extends School{
// 继承了抽象方法的类都必须对父类中的抽象方法进行具体化,具体跟类的重写是一样的
baobei(){
console.log("我是学生,是xx班的")
}
}
五、联合类型和类型保护
1.联合类型
当我们的一个方法可以接受多种类型的参数(parameter),那么此时就要用到联合类型。
class Student {
name: string;
jiaozuoye() { };
}
class Teacher {
name: string;
gaizuoye() { };
}
function f(parameter: Student | Teacher) { }
2.类型保护
不过随着联合类型而来的还有一个问题,假如Student和Teacher中有两个不同的方法,不同类型的参数又怎么确定是否有继承该类型的特殊方法呢?为了解决这个问题,我们引入了类型保护的概念。
as语法
as又叫类型断言,as相当于是人为的判断,比如一个人的名字(name属性)就叫“学生”,那么我们判断他就是学生,让他能够调用学生的方法jiaozuoye(),具体操作如下。
function f(parameter: Student | Teacher) {
if(parameter.name == "学生"){
(parameter as Student).jiaozuoye();
}
}
in语法
in语法比较好理解,比如我们的Student和Teacher中的区别就在于方法交作业和改作业的不同,如果参数有交作业的功能,那我们就判断他为Student,反之就判断他为Teacher,此时可以用in语法。
function f(parameter: Student | Teacher) {
if("jiaozuoye" in parameter){
parameter.jiaozuoye();
}else{
parameter.gaizuoye();
}
}
typeof语法
上面我们举的例子中参数都是自定义的类型,那么对于普通类型我们的类型保护策略是什么呢?举一个新的例子
function f(parameter1: string | number, parameter2: string | number) {
//使用typeof进行类型判断
if (typeof parameter1 === "string" || typeof parameter2 === "string") {
//字符串拼接
return `${first}${second}`;
}
return first + second;
}
instanceof语法
instanceof和typeof语法很像,但是instanceof只能用在类class的保护上。
// 我们先定义一个类,作为我们类型保护的基础
class NumberObj {
count: number;
}
// 实现一个相加的方法
function f(first: object | NumberObj, second: object | NumberObj) {
// 运用instanceof语句对类型进行判断
if (first instanceof NumberObj && second instanceof NumberObj) {
return first.count + second.count;
}
return 0;
}
六、泛型
对于typescript中的泛型,你可以把它理解为参数化类型,把每个参数的类型也封装成参数的形式,而这个封装的方式,就是泛型。
1.函数泛型
如果我们要编写一个函数,接收两个参数,这两个参数可能都为string类型或者都为number类型,最后做拼接操作。
对于这个功能,其实通过上面的联合类型以及类型保护就能实现,但是,我们再分析一下这个功能的条件,“这两个参数可能都为string类型或者都为number类型”,思考一下,我们能不能在调用的时候就直接告诉这个函数传进来的参数类型呢?答案是可以的,泛型就能实现这个功能。
function f<T>(one: T, two: T) {
return `${one}${two}`
}
f<string>("ok", "fine")
这里再多提一嘴,由于typescript具有类型推断功能,所以我们在调用函数的时候甚至不需要写泛型中参数,比如这样:
f("ok", "fine")
当然,我们也可以定义多个泛型。
function f<T, P>(one: T, two: P) {
return `${one}${two}`
}
f<string, number>("ok", 2)
2.类的泛型
初始化类的泛型
创建一个类,顺带把构造函数写好,两个参数:
class Student {
constructor(private name: string, private age: number) { }
}
参数化参数的类型,实现对类的泛型初始化,之后实现一个简单的调用。
class Student<T, P> {
constructor(private name: T, private age: P) { }
}
const stu = new Student<string, number>("小白", 21)
泛型的继承
接着上面Student的例子,现在有一个这样的数据结构:每一个Student都来源于一个Person接口,这个接口中有一个NAME属性(string类型),现在我们需要把一个Student对象中的NAME取出来。
首先我们完善一下interface以及getter函数的部分:
interface Person {
NAME: string
}
class Student<T> {
constructor(private name: T) { }
get getName() {
return this.name
}
}
现在的接口Person和Student类还是没有任何关联的,接下来我们使用泛型的继承将Person中的NAME传递给Student中的name,大家在理解这一点的时候注意两个name的大小写,小写的是类中定义的,大写的是接口传递给类的。
interface Person {
NAME: string
}
class Student<T extends Person> {
constructor(private name: T) { }
get getName() {
return this.name.NAME
}
}
最后调用一下。
const stu = new Student({ NAME: "小白" })
如果大家观察的仔细,我们会发现我们在传递构造函数的时候直接传递进去了一个对象,是带着大括号的。原因其实很简单,我们可以将Person看作是一个要传进去的那个对象的描述,通过extends语句将这个描述传递给Student中的name属性,最后在我们初始化对象的时候,我们看似初始化的是一个对象,但其实我们初始化的还是构造函数中的那个name。
这个地方一开始看的我懵逼了很久,最后我照着别人的代码写了一个实例出来,又反复看了几遍就发现能看懂了。
网友评论