注:本文仅对ts中相对js不一样的地方做了笔记📒哟~想看js教程请挪步👉🏻
简介
Typescript是以javascript为基础构建的语言(是JavaScript的超集),可以在任意支持Javascript的平台中执行,但是它不能直接被js解析器执行,需要通过编译成js语言才可以解析;
安装Typescript
typeScript的命令行工具安装方法如下:
npm install typescript -g
该命令会在全局环境下安装tsc命令,编译一个typeScript文件(后缀为.ts)需要使用命令:
tsc 文件名
使用上述命令编译后,会自动生成对应的.js文件;
也可以使用vscode编辑器自动编译:
- 首先使用在项目文件夹下执行命令,生成对应的配置文件tsconfig.json
tsc --init
并将tsconfig.json文件中的outDir配置为对应的输出路径;
- 在vscode编辑器中选择终端--运行任务,选择typescript并监视,
即可实现监视ts文件的变化,自动生成js文件;
类型声明
类型声明是TS最重要的一个特点,通过类型声明可以指定TS中变量(形参、实参)的类型,在指定类型之后,当为变量赋值时,ts编译器会自动检查值是否符合类型声明,符合才会进行赋值;
let 变量: 类型; // 声明
let 变量: 类型 = 值; // 声明并赋值
function fn(参数: 类型, 参数: 类型): 类型 {
}
类型 | 例子 | 描述 |
number | 1 23 -23 | 任意数字 |
string | 'hi' 'welcome' | 任意字符串 |
boolean | true false | 布尔值 |
字面量 | 12 ‘string’ {} [] | 限制变量的值就是该字面量的值 |
any | * | 任意类型 |
unknown | * | 任意类型 |
void | 空值(undefined) | 没有值(或undefined),一般用于声明函数返回值 |
never | 没有值 | 不能是任何值 |
object | {name: '小明'} | 任意的js对象 |
array | [2, 3, 4] | 任意的js数组 |
tuple | [4, 5] | 固定长度的数组,新增数据类型 |
enum | enum{male, female} | 枚举,新增数据类型 |
注意当声明类型为any时,相当于对该变量关闭了Ts类型检测,但是在将一个any类型的变量a赋值给一个非any变量b时,会将b也变成any类型(容易造成变量类型混乱,所以要慎用any);若变量声明时未定义类型时,默认是any类型(隐式的any);一般用unknown代替any使用,它相当于一个安全类型的any,它不可以随意赋值给其他类型变量;
never表示永远不会返回结果,可以用于函数抛错:
function fn(): never {
throw new Error('抛错喽')
}
{}用于指定对象中可以包含哪些属性:
let person : {
name: string,
class: string,
project?: string
} // 声明person变量
person = {
name: '小明',
class: '一年级'
} // 变量赋值
let dog : {
name: string,
[propName: string]: any
} // 声明dog变量
dog = {
name: '汪汪',
gender: '男',
age: 1
} // 增加了number类型的age与string类型的gender
声明对象时,在属性名后面加上?表示该属性是可选的
声明对象时,加上[propName: string]: any 表示可定义任意类型的对象(propName为属性名,属性名为string类型)
声明数组时,可以在变量前加上类型,表示数组内都是什么类型的数据,或者通过Array<类型>表示;
类型[];
Array<类型> // 泛型
let arr: number[] = [123,456]; // 定义一个数字数组
let arr2: Array<string> = ['345','678']; // 使用泛型定义一个字符串数组
let e: string[]; // 表示e是字符串数组
let f: number[]; // 表示f是数字数组
let g: Array<string> // 表示g是字符串数组
元组是Ts新增的数据类型,元组就是固定长度的数组;元组中的元素不必保持类型一致
语法: [类型, 类型, 类型]
示例:
let h :[string, number, string];
h = ['hi', 1, 'num']
添加内容的时候,必须是所定义的内容:
let h :[string, number, string];
h = ['hi', 1, 'num']
h.push(321)
h.push(true) // Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
console.log(h); // ['hi', 1, 'num', 321, true]
枚举一般用于对已知的数据进行列举:如果枚举中的标识符没有赋值,则值一般为下标,如下例中Male即为0
enum Gender {
Male,
Female
} // 定义Gender为枚举类型
let i: {
name: string,
gender: Gender
} // gender属性为Enum类型
i = {
name: '小明',
gender: Gender.Male
}
外部枚举是使用declare enum定义的枚举类型:
declare enum Directives {
Up,
Down,
Left,
Right
}
let directives = [Directives.Up, Directives.Down, Directives.Left, Directives.Right]
declare定义的类型只会用于编译时的检查,在编译结果中会被删除;
即上述的编译结果为:
let directives = [Directives.Up, Directives.Down, Directives.Left, Directives.Right];
外部枚举与声明语句一样,一般用于声明文件中;
同时使用declare与const也是可以的;
declare const enum Directives {
Up,
Down,
Left,
Right
}
let directives = [Directives.Up, Directives.Down, Directives.Left, Directives.Right]
编译结果为
let directives = [0 /* Directives.Up */, 1 /* Directives.Down */, 2 /* Directives.Left */, 3 /* Directives.Right */];
联合类型
可以使用 | 来连接多个类型(联合类型),联合类型表示可以取值为多种类型中的一种;
let age: number | string
age = 18;
age = '21';
age = true; // 报错 Type 'boolean' is not assignable to type 'string | number'.
当ts不确定一个联合类型的变量到底是哪个类型的时候,只能访问此联合类型的所有类型中共有的属性或方法 ;
function searchAge(age: number | string) {
console.log(age.toString()); // 访问公有方法
console.log(age.length); // 报错:Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.ts(2339)
}
当再次给被联合声明定义的变量赋值后,则此时该变量的类型就会被确定:
let age: number | string;
age = 12 // 此时age确认为number类型
console.log(age.length); // 报错Property 'length' does not exist on type 'number'.
类型断言
类型断言有两种方式:使用as或者使用<>在变量前声明;类型断言用于告诉解析器变量的实际类型;
变量 as 类型
<类型>变量
第二种方式除了作为类型断言外,还可以表示一个泛型,所以在使用类型断言时,一般推荐第一种方式(使用as关键字)
类型推论
如果没有明确的指定类型,那么ts就会依据类型推论的规则推断出一个类型;
如:
let firstName = 'xiao';
firstName = 12 // 报错Type 'number' is not assignable to type 'string'.
就会将firstName断言成string类型,相当于如下代码:
let firstName: string = 'xiao';
firstName = 12 // 报错Type 'number' is not assignable to type 'string'.
即在定义变量的时候同时赋值,则默认该变量为其赋值类型;
let firstName
firstName = 12
firstName = '12'
firstName = {}
若只定义了变量未赋值,则默认类型是any,可任意赋值;上述代码相当于
let firstName: any
firstName = 12
firstName = '12'
firstName = {}
类型别名
类型别名用来给一个类型起个新名字,使用关键字type可以定义类型别名;
type str = string; // 将string类型重命名为str
类型别名常用于定义联合变量:
type nameProp = string | number
let gender: nameProp = 'male';
let age: nameProp = 12
字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串中的一个;也是使用type关键字进行定义;
type strProp = 'students' | 'teachers' | 'schools'
let stu: strProp = 'students';
let tea: strProp = 'teachers'
接口
在面向对象的编程中,接口是一种规范的定义。规范一个类中应该包含哪些属性和方法,同时接口也可以当做类型声明去使用;
接口可以在定义类的时候去限制类的结构,接口中的所有属性都不能有实际的值,只能定义对象的结构;并且在接口中所有的方法都是抽象方法(在类中都要实现);
接口是行为的抽象,而具体怎么行动需要由类去实现(implement),当然也可以对【对象的形状】进行描述~
接口可以继承接口,实现接口的子类也可以同时实现多个接口;
用接口定义对象
interface myInterface {
name: string
age: number
}
const obj: myInterface = {
name: '小明',
age: 12,
} // 接口作为类型声明来使用
定义了接口类型的变量,其属性和方法必须保证与接口定义的相同(不能多也不能少);
interface myInter {
name: string;
sayHello(): void
}
// 用类实现一个接口,即使类满足接口的要求
class MyClass implements myInter {
name: string,
constructor(name: string) {
this.name = name
}
sayHello() {
console.log('大声呼喊')
}
}
用接口定义数组
接口也可以用来定义数组,只要将属性设置为number类型即可:
interface Arr {
[index: number]: string // 定义字符串数组
}
let arr4: Arr = ['23', '34', '45']
用接口定义函数
函数定义有两种形式:函数声明和函数字面量形式
// 函数声明形式--命名函数
function searchText(day: string, text: string): string {
return text + day
}
// 函数表达式形式--匿名函数
const searchText = function(day: string, text: string): string {
return text + day
}
// 函数表达式形式--匿名函数之完整写法
const searchText: (day: string, text: string) => string = function(day: string, text: string): string {
return text + day
}
使用接口定义:
interface searchFuc {
// 格式为(参数: 参数类型, .... , 参数:参数类型): 函数返回值类型
(day: string, text: string): string
}
const searchText: searchFuc = function(day: string, text: string): string {
return text
}
接口继承
接口也可以继承其它的多个接口,
interface ISing {
sing(): void
}
interface IDance {
dance(): void
}
interface IAction extends ISing, IDance{
aciton(): void
}
// IAction 不仅有action方法,还会继承到sing与dance方法
接口也可以继承类:
class action {
name: string
constructor(name: string) {
this.name = name
}
sing(): void {
console.log('唱歌');
}
dance(): void {
console.log('跳舞');
}
}
interface animal extends action {
age: number
}
// animal接口中具有属性name与age,以及sing和dance方法
let dog: animal = {
name: 'wangwang',
age: 2,
sing() : void {
console.log('狗会汪汪叫');
},
dance(): void {
console.log('狗也会乱跳');
}
}
可选变量与任意变量
在定义对象时,有时会 遇到一些属性不一定是每个对象都必有的属性,该变量为可选变量;
同时,在定义对象时,我们也会遇到一开始定义时并不确定都有神马变量的情况,该变量成为任意变量;
interface Person {
name: string;
age: number;
gender?: string; // 可选属性gender
[propName: string]: any // 定义可添加任意类型的任意属性
}
let person: Person = {
name: '123',
age: 12,
hobby: 'reading'
}
一旦定义了任意变量,则确定属性与可选属性的类型都必须是它的类型的子集:
interface Person {
name: string;
age?: number; // 报错:Property 'age' of type 'number | undefined' is not assignable to 'string' index type 'string'.
[propName: string]: string // 定义任意属性皆为string类型
}
interface Person2 {
name: string;
age: string;
// name与age都必须要是string类型
[propName: string]: string
}
一个接口中只能定义一个任意属性,若任意属性包含多个类型,则可以在任意属性中使用联合类型:
interface Person2 {
name: string;
age: number;
[propName: string]: string | number
}
let person: Person2 = {
name: '张张',
age: 34
}
则name就可以和age设置成不同类型的变量~
只读属性
若希望对象中一些字段只能在创建的时候被赋值,则可以用readonly定义只读属性;
interface Ideas {
readonly title: string,
time: string
}
let ideas: Ideas = {
title: '这是个秘密',
time: '20230523'
}
ideas.title = '看看能修改不' // Cannot assign to 'title' because it is a read-only property.ts
类
Ts中除了实现了所有Es6中的类的功能以外,还添加了一些新的用法~
class animal {
// 要先声明类型
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
say() {
console.log('我是' + this.name);
}
}
let dog = new animal('wangwang', 12);
dog.say() // 我是wangwang
使用extends进行继承(与js一样);
存取器
使用getter和setter可以改变属性的赋值和读取行为:
在读取时会调用类中的setter方法,赋值(修改)时会调用类中的getter方法;
class Flower {
name: string
constructor(name: string) {
this.name = name
}
get Name() {
console.log('数据被获取了');
return this.name
}
set Name(newValue: string) {
console.log('数据被改变了', newValue);
this.name = newValue
}
}
let meigui = new Flower('玫瑰花')
console.log(meigui.name); // 玫瑰花
console.log(meigui.Name); // 数据被获取了
meigui.Name = '月季花' // 数据被改变了 月季花
console.log(meigui.name); // 月季花
此处补充下Ts中的private/protected/public/readonly修饰符:
public修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public的;
private 修饰的属性或方法是私有的,只在当前类中可见,对实例对象以及子类都是不可见的;
class Animal {
private run() {
console.log('run')
}
protected move() {
this.run() //对当前类是可见的
console.log('move')
}
_run() {
this.run()
this.move() //对当前类是可见的
console.log('_run')
}
}
const a = new Animal()
// a.run() Property 'run' is private and only accessible within class 'Animal'.
//子类
class Dog extends Animal {
bark() {
// this.run() 对子类以及实例是不可见的Property 'run' is private and only accessible within class 'Animal'.
console.log('汪!')
}
}
const dog1 = new Dog()
// dog.run() 属性“run()”为私有属性,只能在类“Animal”内部访问。
protected 修饰的属性或方法是受保护的,与private的区别是它在其子类中是可以被访问的;但是在父类或子类的实例中都是不能被访问的
class Flower {
public name: string
private age: number
protected count: number
public constructor(name: string, age: number, count: number) {
this.name = name
this.age = age
this.count = count
}
protected getName() {
console.log('获取name');
}
private getAge() {
console.log('获取age');
}
}
class meiGui extends Flower {
constructor(name: string, age: number, count:number) {
super(name, age, count)
}
showMeigui() {
console.log(this.name + this.age + this.count);
// 此处子类中仅可以访问name与count(protected),age不能被访问
}
}
class Animal {
protected move() {
console.log('move')
}
run() {
this.move()
console.log('run')
}
}
const b = new Animal()
// b.move() 通过父类实例对象访问不到move()这个方法的,只能在类“Animal”及其子类中访问。
//子类继承父类
class Dog extends Animal {
bark() {
this.move()
console.log('汪!')
}
}
const dog = new Dog()
// dog.move() 通过子类实例对象也访问不到move()这个方法的,只能在类“Animal”及其子类中访问。
readonly为只读属性,用来防止在构造函数之外对属性进行赋值(只能在构造函数里进行赋值);
class Y {
readonly age: number
constructor(age: number) {
this.age = age // 在构造函数中可以访问
}
setAge() {
console.log(this.age); // 可以读
this.age = 15 // 不可以改,Cannot assign to 'age' because it is a read-only property.
}
}
以上四个修饰符都可以直接放在参数前面,表示创建并且初始化:
class Y {
constructor(readonly age: number) {
this.age = age // 在构造函数中可以访问
}
setAge() {
console.log(this.age); // 可以读
this.age = 15 // 不可以改,Cannot assign to 'age' because it is a read-only property.
}
}
抽象类
abstract用于定义抽象类和其中的抽象方法
①抽象类是不允许被实例化的
abstract class Y {
age: string
constructor(age: string) {
this.age = age // 在构造函数中可以访问
}
abstract setAge(): string
}
let y = new Y('12') // Cannot create an instance of an abstract class
②抽象类中抽象方法必须由子类实现
abstract class Y {
age: string
constructor(age: string) {
this.age = age
}
abstract setAge(): string // 不能有具体实现
}
class y extends Y {
constructor(age: string) {
super(age)
}
setAge(): string {
console.log('拿到数据了'); // 在子类中必须实现抽象类中的方法
return this.age
}
}
类与接口
接口也可以对类的一部分行为进行描述
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口,用implements关键字来实现,这个特性大大提高了面向对象的灵活性;
interface ISing {
sing()
}
class per implements ISing {
sing() {
console.log('人会唱歌');
}
}
class ani implements ISing {
sing() {
console.log('动物也会唱歌');
}
}
类也可以同时实现多个接口,
interface ISing {
sing()
}
interface IDance {
dance()
}
class per implements ISing, IDance {
sing() {
console.log('人会唱歌');
}
dance() {
console.log('人会跳舞');
}
}
泛型
泛型是解决类、接口、方法的复用性以及对不确定数据类型的支持;
用法:在函数名称/接口名称/类名称的后面添加< > (尖括号),尖括号中添加类型变量
// 泛型类
function fn<T>(a: T): T {
//参数a和function返回值皆是泛型T
}
function fn2<T, K>(a: T, b: K): T{
return a
}
// 参数a和function返回值皆是泛型T,参数b是泛型K
示例:定义一个函数,传入两个参数,第一个参数是数据(数据类型不定),第二个参数是数量,根据数量产生对应个数的数据,存放在一个数组里面;
// 不确定传入数据的类型,因此将data设为泛型,函数返回值也为泛型数组
function setArr<T>(data: T, count: number): T[] {
const arr: T[] = []
for (let index = 0; index < count; index++) {
arr.push(data)
}
return arr
}
let result = setArr(123, 3); // 走了类型断言
console.log(result); // [123, 123, 123]
let result2 = setArr<string>('123', 3); // 使用的时候确定类型
console.log(result2); // ['123', '123', '123']
示例:定义一个泛型接口
interface Ifun {
<T>(data: T, num: number): Array<T>
}
let saySch: Ifun = function<T>(data: T, num: number): T[] {
let arrs: T[] = []
for (let index = 0; index < num; index++) {
arrs.push(data)
}
return arrs
}
示例:定义一个泛型类
class Tree<T> {
name: T
age: number
constructor(name: T, age: number){
this.name = name;
this.age = age;
}
}
let yeTree = new Tree<string>('椰子树', 12);
let yaTree = new Tree<boolean>(true, 10)
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作他的属性或方法,因此需要对泛型进行约束,只允许这个函数传入包含某些属性或方法的变量,这就叫泛型约束;
示例:获取数据的长度(则要求此数据包含.length属性)
// 获取数据的长度
interface Ilength {
length: number
}
function searchLen<T extends Ilength>(data: T): number {
console.log(data.length);
return data.length
}
searchLen('123') // 3
searchLen(123) // 报错 Argument of type 'number' is not assignable to parameter of type 'Ilength'
声明合并
如果定义了两个相同名字的函数、接口或类,那么就可以将其进行合并;
函数的合并
可利用函数重载定义多个函数类型:
function test(data: number): number
function test(data: string): string
// 就可以合并为
function test(data: number | string) : number | string
接口合并
接口中的属性在合并时会简单的合并到一个接口中去:
要注意合并的属性类型必须是唯一的
interface test {
data: string
}
interface test {
num: number
}
let one: test = {
data: '124',
num: 12
}
// 会将接口自动合并为
interface test {
data: string
num: number
}
类的合并
类的合并与接口的合并规则是一样的哟