前言
前段时间学习TS
,一直没时间总结,今天乘着有时间,我将我在学习过程中的一些核心知识整理成文,方便以后查阅
本文特点:小巧,几分钟可以快速看完
收获:阅读本文,你将可以快速上手JS
,了解TS的核心基础知识
附:本文结构预览,如下
简介
TypeScript
是JavaScript
的超集,为大型应用而生。
有两个特点:
一是强类型;
二是基于类的面向对象编程
数据类型
TS
中的数据类型有很多,如下:
Number
类型
let a: number = 5
复制代码
如上,声明一个Number
类型的a
并赋值为数字5
;可以看TS
中声明变量数据类型时,格式为变量名后面加: xx类型
String
类型
let str: string = 'hello ts'
复制代码
Boolean
类型
let isDone: boolean = true
复制代码
Null
和Undefined
类型
let n1: null = null
let u1: undefined = undefined
// -------------------------------
let n2: null = undefined
let u2: undefined = null
复制代码
定义为null
或者undefined
类型的变量,赋值可以为null
和undefined
其中之一
Void
类型
function fn():void{
console.log('This is a fn,but return is void')
}
复制代码
void
用于表示返回空
Any
类型
let any1: any = 'xxx'
复制代码
Any
类型的变量可以赋值任意类型的值 7. Array
类型
let arr: number[] = [1, 2, 3];
//或者
let arr: Array<number> = [1, 2, 3];
复制代码
Enum
类型
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
复制代码
需要注意,以上仅列举了部分常用的数据类型,其他可移步官网
类型声明
TS
中提供了一些基本类型,另外类也可以当类型。但是有的时候我们需要一些更灵活的类型,这就需要自定义一些类型或者叫类型声明
声明类型需要借助一个关键字type
例如
type User = {
name: string;
age?: number;
}
复制代码
以上代码定义了一个User
类型,该类型必须有name
属性类型为string
,可以选择有或者没有类型为number
的age
属性
let user: User;
user = {
name: 'Alice'
}
复制代码
上述代码,声明一个user
变量要求是我们定义的User
类型并赋值,赋值时需要值与定义的类型对应
当然不仅仅用于变量,自定义类型跟普通类型一样,可以用于所有能用类型的地方,没有限制,比如函数的形参等等
function show(obj: User) {
console.log(obj.name)
}
show(user);
复制代码
可以看到使用type
关键字可以很方便的创建一个新类型
类型断言与类型守卫
先来看个例子
type User = {
name: string;
age: number;
}
function showUser(user: User) {
console.log(user.name);
console.log(user.age);
}
showUser({ name: 'Alice', age: 12 })
复制代码
如上,showUser
函数执行传递的参数是符合类型要求的。但是如果不符合要求的参数就会报错
let errorType = '';
showUser(errorType); //错误
复制代码
正常编写代码一般不会出这样的问题,但是这个数据有可能来自运行时的其他地方(比如数据库、第三方库、用户输入等)。 我们知道语言在运行时是不具有类型的,那我们在运行时如何保证和检测来自其他地方的数据也符合我们的要求呢? 这就类型断言要干的事
所谓断言就是断定、确定、绝对的意思;所以简单来讲,类型断言就是保证数据类型一定是所要求的类型
类型断言还需要借助类型守卫函数,类型守卫函数就是用于判断未知数据是不是所需类型
function isUser(arg: any): arg is User {
if (!arg) {
return false;
}
else {
if (typeof arg.name == 'string' && typeof arg.age == 'number') {
return true;
} else {
return false;
}
}
}
复制代码
可以看到类型守卫函数与普通函数没多大区别,唯一需要注意其返回值类型比较特殊特殊,格式:x is y
,表示x
是不是y
类型
if (isUser(errorType)) {
showUser(errorType);
}
复制代码
经过这样的类型断言后就不会报错了
类(class)和修饰符
类
我们知道,类class
出现的目的,其实就是把一些相关的东西放在一起,方便管理
类中包含两个东西(也叫成员):
- 属性
- 方法
类的成员就是类中所有的属性和方法
TS中通过class
关键字可以方便的定义一个类
class Person{
name:string;
age:number;
show(){
console.log(`我叫${this.name},今年${this.age}了`);
}
}
复制代码
通过new
关键字可以方便的生产一个类的实例对象,这个生产对象的过程叫实例化
let p1 = new Person('Alice',12)
复制代码
如上,p1
就是Person
类的一个实例
还有一点需要注意,就是在实例在new
出来的时候,它实际是调用了类中的一个方法进行初始化,这个方法被叫做构造器;一般都会在类中显示地写上constructor
方法,如果没有显示定义的constructor
,就会调用系统隐示的constructor
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
复制代码
类中的成员修饰符
访问修饰符
访问修饰符的作用就是用于限制别人乱用类中的东西
-
public
公开的,谁都能用(默认public) -
private
私有的,仅类自己里头才能使用 -
protected
受保护的,仅仅类和类的子类能使用
使用访问修饰符的建议:尽量使用private
,一般所有属性都是private
,要想访问私有属性可以通过访问器
class Person{
private _num:number = 12;
public get num(){
return this._num;
}
public set num(val:number){
this._num = val;
}
}
let p1 = new Person();
console.log(p1.num)
p1.num = 5;
console.log(p1.num)
复制代码
事实上ts
的访问器其实借助的就是js
的访问器,而js
的访问器是到高版本里才有的 所以在通过tsc
命令编译的时候有个注意点: ts
默认生成ES4
的代码,我们编译时需要通过--target 某版本
指定ts编译的版本
tsc --target ES5 1.js
复制代码
当然也可以在tsconfig.json
文件中配置 访问器的好处在于:安全和方便
只读修饰符
readonly
只能读不能写
class Person{
readonly name = 'Alice';
}
let p = new Person();
console.log(p.name);
复制代码
需要注意的时,即使是readonly
的东西,在初始化之前是可以写,即在constructor
中可以初始化或更改
class Person{
readonly name:string;
constructor(name:string){
this.name = name;
}
}
复制代码
所以,我们知道了,readonly
的属性,仅两个地方可以写:
- 在声明同时赋予初始值
- 在构造函数中赋值或者修改初始值
静态修饰符
static
静态的 通过static
修饰的成员叫静态成员,静态成员无需实例化,直接通过类名调用
class Person{
static a = 98;
}
console.log(person.a)
复制代码
静态成员通常用于整个类所共有的一些东西
注意点
以上三种修饰符:访问修饰符、只读修饰符和静态修饰符可以组合修饰同一成员 但需要注意
- 修饰符是可选的,在没有写任何修饰符,默认有个
public
- 同类修饰符只能有一个
- 三种修饰符有先后顺序,分别是:访问、静态、只读 即:
【public/static/protected】 【static 】【readonly】
类的继承
我们知道js
中有继承,最开始js
是使用函数来模拟实现类的,一直到ES6
出现,才开启了class
以及extends
等相关关键字的使用。那为什么会有继承呢? 事实上,继承的好处在于,可以更好的重用代码,以及后期更好的维护代码
TS
中的继承ES6
中的类的继承极其相识,子类可以通过extends
关键字继承一个类 例如:
class Person{
name:string;
age:number;
constructor(name,age){
this.name = name;
this.age = age;
}
}
class student extends Person{
score:number;
constructor(name,age,score){
super();
this.score = score;
}
}
复制代码
可以看见,跟ES6
一样,子类构造函数必须加上super()
执行父类的构造constructor
函数
所以,大家常常说,一个子类实例,同时也是父类的实例
继承的格式:
class A {}
class B extends A {
constructor() {
super();
}
}
复制代码
如上,B继承A,那B被称为父类(超类),A被称为子类(派生类) 子类实例是可以继承父类所有的public
和protected
的属性和方法
除了继承,面向对象还有一个特征:多态 js和ts中多态其实很常见,可以理解为多种状态,比如代码在运行时才能决定具体执行哪个函数
抽象类
抽象就是指不具体的,所以抽象类就是指不具体的类。所以抽象类自身没有什么功能,通常作为父类类使用
定义一个抽象类,使用abstract class
两关键字定义
abstract class A{
abstract fn():number;
}
复制代码
抽象类规定了所有继承自它的非抽象子类必须实现它的所规定的功能和相关操作,否则会报错
class B extends A{
constructor(){
super();
}
fn():number{
return 1
}
}
复制代码
需要注意,抽象类仅仅作为基类,不能new
let b = new B();//可以
let a = new A();//报错
复制代码
接口
接口是一种规范,跟抽象类有点类似
通过 interface
关键字定义接口,格式如下:
interface xxx{
属性: 类型 ;
...
(函数参数) : 返回值类型
...
}
复制代码
interface
一般用于规范三个东西:
- 函数
- 类
- 构造器
函数interface
函数interface
可规范函数的参数及返回值
interface xxx{
(参数,...):返回值类型
}
复制代码
例如
interface SearchFn {
(key: string, page?: number): number[]
}
const search: SearchFn = function (key: string, page: number) {
return [1, 2, 3]
}
复制代码
我们可以看到,interface
规范函数时,interface
相当于type
当一个函数类型是接口时,要求:
- 参数名可不一致,但参数的值类型必须与接口定义的对应,且参数可以少不可多;
- 返回值必须有,且与接口定义的一致;
类interface
我们知道,继承的好处是复用代码,并且层级关系清晰。但JavaScript
中继承是单继承,一个类只能有一个父类。而在TypeScript
中interface
会显得更加灵活,因为interface
可以多实现 例如:
interface serialization {
toJSON(): object;
formJSON(json: object): void;
}
class Box implements serialization {
width: number;
height: number;
constructor(width:number,height:number){
this.width = width;
this.height = height;
}
toJSON(): object {
return { width: this.width, height: this.height }
}
formJSON(json: object): void {
if (isSize(json)) {
this.width = json.width;
this.height = json.height;
}
}
}
function isSize(json: any): json is { width: number, height: number } {
if (typeof json != 'object') {
console.log('必须是object类型');
} else {
if (typeof json.width != 'number' || typeof json.height != 'number') {
console.log('width 和 height 必须是number类型!!')
}
}
return true;
}
let box = new Box(50,50)
复制代码
如上,通过implements
关键字可以让一个类实现一个接口,要求必须实现时间接口定义的方法,否则会出错
构造函数interface
构造函数interface
比较特殊,是通过赋值的形式来实现,并且得跟普通interface
区分开,普通interface
还是使用implements
。另外在接口中使用new
指代构造器
interface BoxConstructorInterface{
new (a:string)
}
interface BoxInterface{
show(a:number):void;
}
const Box:BoxConstructorInterface = class implements BoxInterface {
private a:string;
constructor(a:string){
this.a = a;
}
show(a:number){
console.log(this.a)
}
}
复制代码
另外,跟类一样,interface
也能继承另外一个interface
例如:
interface A { }
interface B extends A { }
class C implements B { }
复制代码
所以,我们知道了,接口本身只是一种规范,里头定义了一些必须有的属性或者方法,接口可以用于规范function
、class
或者constructor
,只是规则有点区别
泛型
泛型可以理解为宽泛的类型,通常用于类和函数
泛型类
泛型可以用于类和构造器,例如:
class Person<T>{
private _value: T;
constructor(val: T) {
this._value = val;
}
}
let p = new Person<number>(12)
复制代码
如上,<T>
表示传递一个T
类型,在new
的时候才把具体类型传入。其中T是变量可改,但通常比较常见就是写T
之前说TypeScript
类型的时有说到数组,其实数组本质就是一个泛型类
let a = new Array<number>();
复制代码
泛型函数
泛型可以用于普通函数,例如:
function fn<T>(arg: T): T {
return arg;
}
fn<number>(12);
复制代码
其实不管是用于类还是用于函数,核心思想都是:把类型当一种特殊的参数传入进去
需要注意的是泛型也可以“继承”,但表示的是限制范围 例如
class Person<T extends Date>{
private _value: T;
constructor(val: T) {
this._value = val;
}
}
let p1 = new Person(new Date())
class MyDate extends Date{}
let p2 = new Person(new MyDate())