typescript
说明
typescript是javascript的超集。
ts文件中如果只有正常的js代码,可以再谷歌浏览器直接引入,否则需要先解析为js文件再引入js文件。
typescript的核心原则之一是对值所具有的结构进行类型检查。
一.安装
npm install -g typescript
检验命令
tsc -v
二.编译
tsc myTs.ts
编译结果
myTs.js
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6IfuHvn8-1673689252920)(img.png)]
编译前后区别
js文件中,没有形参定义的类型;
ts中使用的let编译为var。
配置自动编译ts文件
tsc --init
生成 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yYRF4B0f-1673689252922)(img_1.png)]文件
这里我用的webstorm
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jNoD4GgS-1673689252922)(img_2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QjOBAVRZ-1673689252922)(img_3.png)]
配置之后,自动编译生效
语法
一.类型注解
一种轻量级的为函数或变量添加的约束
(function () {
function sayHi(str: String) {
return "hello" + str;
}
let str = "花花";
let arr = [1, 2, 3];
console.log(sayHi(str));
// console.log(sayHi(arr)) // 这里会报错,arr是Object类型
})()
二.接口
接口是对对象的状态(属性)和行为(方法)的抽象(描述),是一种对实例的约束。
1.实例的属性和方法是否和定义的一致
2.变量名前可以使用readonly设置只读
3.变量名后可以使用?表示非必须
(function () {
interface IPerson {
readonly id: number,
firstName: string,
lastName: string,
age?: number
}
class Person {
firstName: string
lastName: string
fullName: string
constructor (firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
this.fullName = this.firstName + " " + this.lastName;
}
}
function showFullName(person: IPerson) {
return person.firstName + " " + person.lastName;
}
const person = new Person("张", "三")
// const person = new Person("张") 这里会报错,不符合类形参的规范
console.log(showFullName(person));
// Argument of type 'Person' is not assignable to parameter of type 'IPerson'.
// Property 'id' is missing in type 'Person' but required in type 'IPerson'.
person.id = 12;
// Property 'id' does not exist on type 'Person'.
})()
三.基本类型
变量定义好的类型不能重新赋值为其他类型。
Boolean, String, Number
let a: boolean = true; // Boolean类型
let b: string = "张三"; // String类型
let c: number = 12; // Number类型
// a = 13 报错
undefined, null
undefined和null都可以作为其他类型的子类型(非严格模式下)
let und: undefined = undefined;
let nul: null = null;
let num: number = undefined;
let str: string = null;
console.log(und, nul, num, str);
数组
// 第一种
// let arr: number[] = [1, 32, 543];
let arr: number[] = [1, 2, 4, ""];
// 第二种(泛型)
// let arr2: Array<number> = [2, 5, 7]
数组中数据类型不正确会报错,数据类型必须和定义的是一致的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FYpUl4Bv-1673689252923)(img_4.png)]
元组
元组类型:数据的类型、位置、个数都要和定义的保持一致。
let arr3: [String, Number, Boolean] = ["张三", 12, false];
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiAAjBe6-1673689252923)(img_5.png)]
枚举
枚举是对javascript标准数据类型的补充,
枚举中的每个数据值都可以叫元素,每个元素都有自己的编号,默认递增(类似下标)。
枚举中的元素可以通过编号获取名字。
enum Color {
red,
blue,
green
}
let color: Color = Color.red;
enum Color2 {
red = 2,
blue = 4,
green = 7
}
let color2: Color2 = Color2.red;
let color3: string = Color[1];
console.log(color, color2, color3)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OWBnH2Qo-1673689252923)(img_8.png)]
any类型
当数组中类型、个数、位置都不确定时,可以用any类型
let str1: any = 100;
str1 = "张三"
let arr4: any[] = [123, "z行三", true];
console.log(str1, arr4)
void类型
表示函数没有返回值
function showMsg(): void {
console.log("void表示没有返回值");
// return;
// return undefined;
// return null
return 123
}
console.log(showMsg());
上面代码中依次打印undefined, undefined, null, 123,但是123时编辑器中会有错误提示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zPxPU1Sg-1673689252923)(img_9.png)]
let vd: void = undefined;
// 表示生命一个void类型的数据,意义不大。
object类型
function getObj(obj: object):object {
console.log(obj);
return {
name: "zhangsan",
age: 12
}
}
console.log(getObj({}))
console.log(getObj(new String("123")))
联合类型
联合类型表示取值可以为多种类型中的有一种
function getString(str: number|string):string {
// return str; 这里会报错,因为返回值时string。
return str.toString()
}
console.log(getString(123));
console.log(getString("123"));
类型断言
1.<类型>变量名
2.变量名 as 类型
当我们不确定变量具体的类型时,可以使用类型断言指定为我们指定的数据类型
function getStrFn(x: string|number):number {
if ((<string>x).length) {
return (x as string).length;
} else {
return x.toString().length;
}
}
console.log(getStrFn(123));
console.log(getStrFn("423"));
类型推断
在typescript中,定义变量时,没有明确的指定类型时,将会根据赋值推测出一个类型,没有赋值即为any
函数类型
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。
它就像一个只有参数列表和返回值累心通过的函数定义,参数列表里的每个参数都需要名字和类型。
(function () {
// 这里定义了两个参数及它们的类型,还有返回值的类型
interface ISearchFunc {
(source: string, substr: string): boolean
}
// 使用了定义好的接口来规范函数
const searchFunc: ISearchFunc = function (source: string, substr: string): boolean {
return source.search(substr) > -1;
}
console.log(searchFunc("今天你做核酸了吗", "吃"));
console.log(searchFunc("今天你做核酸了吗", "核酸"));
})()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13MfcQtw-1673689252924)(img_10.png)]
类类型
定义一个类,这个类的类型是定义好的接口。
类可以实现多个接口,接口中的内容给必须要实现。
(function () {
interface IFly {
fly();
}
interface ISwim {
swim();
}
// 这里继承了两个接口
class Person implements IFly, ISwim {
fly () {
console.log("fly");
}
swim () {
console.log("swim");
}
}
const person = new Person();
person.fly();
person.swim();
})()
扩展:接口可以继承多个接口
// 扩展:接口可以继承多个接口
interface FlyAndSwim extends IFly, ISwim { };
class Person2 implements FlyAndSwim {
fly () {
console.log("fly2");
}
swim () {
console.log("swim2");
}
}
const person2 = new Person2();
person2.fly();
person2.swim();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IGCbrXbF-1673689252924)(img_11.png)]
总结:接口继承接口(extends),类实现接口(implements)。
类
可以理解为模板,通过模板可以实例化对象(面向对象)。
(function () {
class Person {
readonly id: number
name: string
age: number
sex?: string
constructor(id: number, name: string, age: number, sex?: string) {
console.log(`id是${id}的人叫${name},今年${age}岁,性别${sex ? sex : "不明"}`)
}
}
const person = new Person(0, "张三", 18);
})()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ES5I8Kw-1673689252924)(img_12.png)]
继承
类和类可以实现继承,使用extends。
子类可以调用父类的构造函数和实例方法,使用super关键字。
子类可以重写父类的实例方法。
(function () {
class Father {
name: string
age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age
}
sayHi(str: string) {
console.log(`我是${this.name},${str}`)
}
}
class Children extends Father {
constructor(name: string, age: number) {
super(name, age);
}
sayHi() {
super.sayHi("我是子类")
}
}
const father = new Father("father", 40);
const child = new Children("child", 12);
father.sayHi("我是父类");
child.sayHi();
})()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNKOGIOS-1673689252924)(img_13.png)]
多态
父类型的引用指向了子类型的对象,不同类型的对象引用相同的方法,产生了不同的行为。
(function () {
class Father {
name: string
constructor (name: string) {
this.name = name;
}
sayHi () {
console.log(`I am ${this.name}`);
}
}
class Child1 extends Father {
hobby: string
constructor(name: string, hobby: string) {
super(name);
this.hobby = hobby;
}
sayHi () {
super.sayHi();
console.log(`I like ${this.hobby}`);
}
}
class Child2 extends Father {
sport: string
constructor(name: string, sport: string) {
super(name);
this.sport = sport;
}
sayHi() {
console.log(`I am ${this.name}, I like ${this.sport}`)
}
}
const father = new Father("张三");
const child = new Child1("张三的大儿子", "computer games");
const child2 = new Child2("张三的二儿子", "basketball");
father.sayHi();
child.sayHi();
child2.sayHi();
function run (one: Father) {
one.sayHi();
}
run(child)
})()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CZaUyO3y-1673689252925)(img_14.png)]
默认,私有与受保护的修饰符
public
可以自由的访问程序里定义的成员,默认是public
先来声明一个基本的类
class Person {
// 属性
name: string;
// 构造函数
constructor(name: string) {
this.name = name;
}
// 方法
eat() {
console.log('吃')
}
}
const per = new Person('狗蛋');
console.log(per.name);
per.eat();
这里per.name及per.eat都可以访问,可以看作是:
class Person {
// 属性
public name: string;
// 构造函数
public constructor(name: string) {
this.name = name;
}
// 方法
public eat() {
console.log('吃')
}
}
const per = new Person('狗蛋');
console.log(per.name);
per.eat();
我们把name属性为私有属性
private
不能在声明它的类的外部访问
class Person {
// 属性
private name: string;
// 构造函数
public constructor(name: string) {
this.name = name;
}
// 方法
public eat() {
console.log('吃')
}
}
const per = new Person('狗蛋');
console.log(per.name);
per.eat();
访问name出现了报错
接下来验证子类继承父类后,是否可以访问私有变量。
class Person {
// 属性
private name: string;
// 构造函数
public constructor(name: string) {
this.name = name;
}
// 方法
public eat() {
console.log('吃')
}
}
const per = new Person('狗蛋');
console.log(per.name);
per.eat();
class Student extends Person {
constructor(name: string) {
super(name);
}
show() {
console.log("我的名字是" + this.name);
}
}
这里子类不可以访问父类的私有属性
protected
与private修饰符的行为相似,但是protected成员在派生类中仍然可以访问。
在外部访问protected修饰的属性
class Person {
// 属性
protected name: string;
// 构造函数
public constructor(name: string) {
this.name = name;
}
// 方法
public eat() {
console.log('吃')
}
}
const per = new Person('狗蛋');
console.log(per.name);
per.eat();
类的外部不可以访问protected修饰的变量
子类访问父类proteced修饰的属性
class Person {
// 属性
protected name: string;
// 构造函数
public constructor(name: string) {
this.name = name;
}
// 方法
public eat() {
console.log('吃')
}
}
// const per = new Person('狗蛋');
// per.eat();
class Student extends Person {
constructor(name: string) {
super(name);
}
show() {
console.log("我的名字是" + this.name);
}
}
const per = new Student('狗蛋');
per.show()
子类可以访问父类protected修饰的属性。
readonly修饰符
readonly是一个关键字,对类中的属性成员进行修饰,修饰后,该属性成员就不能在外部被随意的修改了。
1.构造函数中,可以对只读的属性成员的数据进行修改,普通方法不可以修改;
2.如果构造函数中没有任何参数,类中的属性成员此时已经使用readonly进行修饰了,那么外部也不能对这个属性值进行更改的
3.构造函数的参数可以使用readonly进行修饰,一旦修饰了,那么该类中就有了这个只读的成员属性,这里叫做参数属性,外部可以访问,但是不能修改
4.构造函数中的参数可以使用public及private和proteced进行修饰,类中会自动添加这个属性成员。
接下来我们通过代码逐条进行验证
1.构造函数修改只读数据
class Person {
readonly name: string = "张三";
constructor(name: string) {
this.name = name;
}
}
const per = new Person("李四");
console.log(per.name);
那么如果是类中的普通方法呢
class Person {
readonly name: string = "张三";
constructor(name: string) {
this.name = name;
}
changeName() {
this.name = "王五"
}
}
const per = new Person("李四");
console.log(per.name);
2.无参数时,从外部修改readonly修饰的属性。
class Person {
readonly name: string = "张三";
constructor() {}
}
const per = new Person();
per.name = "李四"
3.readonly修饰构造函数的参数
先看一段代码
class Person {
readonly name: string = "张三";
constructor(readonly name: string) {
this.name = name;
}
}
这里就是上面所说,一旦使用readonly修饰了构造函数的参数,类中就自动添加了对应的成员属性。
readonly修饰参数,可以正常访问
class Person {
constructor(readonly name: string) {
this.name = name;
}
show() {
console.log(this.name);
}
}
const per = new Person("王五");
per.show();
外部不能修改
class Person {
constructor(readonly name: string) {
this.name = name;
}
show() {
console.log(this.name);
}
}
const per = new Person("王五");
per.name = "赵六"
per.show();
4.public、private、protected修饰,类中自动添加成员属性
class Person {
private name: string;
constructor(private name: string) {
this.name = name;
}
}
const per = new Person("王五");
存取器
让我们可以有效的控制对对象成员的访问,通过getters和setters进行操作
class Person {
firstName: string;
lastName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
console.log("调用了get");
return this.firstName + "_" + this.lastName;
}
set fullName(val: string) {
console.log("调用了set");
this.firstName = val.split("_")[0];
this.lastName = val.split("_")[1];
}
}
const per = new Person("上官", "老大");
console.log(per.fullName);
按照结果我们可以看到:
1.per对象是拥有fullName的;
2.在 访问fullName时,调用了get
class Person {
firstName: string;
lastName: string;
constructor(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
console.log("调用了get");
return this.firstName + "_" + this.lastName;
}
set fullName(val: string) {
console.log("调用了set");
this.firstName = val.split("_")[0];
this.lastName = val.split("_")[1];
}
}
const per = new Person("上官", "老大");
per.fullName = "上官_老二";
这里表明,设置firstName和lastName,我们可以使用构造函数的方式,也可以使用set的方式。
静态成员
在类中通过static修饰的属性或方法称为静态成员
class Person {
name: string
static age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
show() {
console.log(this.name);
}
}
age是静态属性,不能使用this.或实例对象去调用;
(function () {
class Person {
name: string
static age: number = 12;
constructor(name: string) {
this.name = name;
}
show() {
console.log(this.name);
}
}
const per: Person = new Person("张三");
console.log(per);
console.log(Person.age); // 通过类名访问该成员数据
})()
构造函数不能使用static进行修饰。
抽象类
抽象类为子类服务。
抽象类包含抽象方法,也可以包含实例方法。
(() => {
abstract class Animal {
abstract eat() {
}
move() {
}
}
})()
1.抽象方法一般没有任何具体内容的实现。
正确的写法如下
(() => {
abstract class Animal {
abstract eat() ;
move() {
}
}
})()
2.抽象类不能被实例化。
(() => {
abstract class Animal {
abstract eat()
move() {
}
}
const ani = new Animal();
})()
3.继承抽象类必须实现抽象方法
(() => {
abstract class Animal {
abstract eat()
move() {
}
}
class Person extends Animal {
}
})()
(() => {
abstract class Animal {
abstract eat()
move() {
}
}
class Person extends Animal {
eat() {
console.log("吃熟食");
}
}
const per = new Person();
per.eat();
})()
函数
ts的函数可以指定参数数据类型和函数的返回值类型
(() => {
// 函数式声明
function add(x: number, y: number): number {
return x + y;
}
// 表达式声明
const minus = function (x: number, y: number): number {
return x - y;
}
})()
函数的完成写法
// 相当于函数add2要符合function (x: number, y: number):number { return x + y }的格式
const add2: (x: number, y: number) => number = function(x: number, y: number): number {
return x + y;
}
可选参数和默认参数
可选参数:使用?进行修饰
默认参数:在声明参数时赋予默认值
(() => {
class Person {
name: string
age: number
address: string
constructor(name: string, age: number = 18, address ?: string) {
this.name = name;
this.age = age;
this.address = address;
}
}
const per = new Person("张三", 22, "解放路");
const per2 = new Person("李四");
console.log(per);
console.log(per2);
})()
剩余参数
剩余参数也叫rest参数,使用…args来表示,放在所有参数的末尾。
(() => {
function show(str: string, ...args: string[]) {
console.log(str);
console.log(args);
}
show("a", "b", "c", "d")
})()
函数重载
函数名相同,函数的参数类型及个数不同
先看一个例子
function add(x: string|number, y: string|number): string|number {
if (typeof x === 'string' && typeof y === 'string') {
return x + y;
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y;
}
}
console.log(add(1, '3'))
这里参数不符合条件,打印出了undefined。
增加函数重载代码。
(() => {
function add(x: string, y: string): string
function add(x: number, y: number): number
function add(x: string|number, y: string|number): string|number {
if (typeof x === 'string' && typeof y === 'string') {
return x + y;
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y;
}
}
console.log(add(1, '3'))
})()
ts会有明确的报错提示。
泛型
单个泛型参数
在定义函数、接口、类的时候不能预先确定要使用的数据的类型,而是在使用函数、接口、类的时候才能确定数据的类型。
function returnArr<T>(value: T): T[] {
const arr: Array<T> = [value]
return arr;
}
console.log(returnArr("123"))
console.log(returnArr<string> ("123"))
根据效果,个人认为泛型可以自动做一个类型推断
多个泛型参数。
function fn<T, K>(value1: T, value2: K): [T, K] {
return [value1, value2]
}
fn<string, number>("1", 2);
泛型接口
在定义接口时,为接口中的属性或方法定义泛型类型
在使用接口时,再指定具体的泛型类型
(() => {
interface Animal<T> {
data: Array<T>
add: (t: T) => void
get: (id: number) => T
}
class Dog {
id ?: number
name: string
constructor(name: string) {
this.name = name;
}
}
class Dogs implements Animal<Dog> {
data: Array<Dog> = []
add(dog: Dog): void {
dog.id = Date.now() + Math.random();
this.data.push(dog);
}
get(id: number): Dog {
for (let i = 0, len = this.data.length; i < len; i++) {
if (this.data[i].id === id) {
return this.data[i];
}
}
}
}
const dogs = new Dogs();
dogs.add(new Dog("大黄"));
dogs.add(new Dog("小黑"));
console.log(dogs);
})()
泛型类
定义一个类,类中的属性值是不确定的,方法中的参数及返回值的类型也是不确定的,这种情况使用泛型类。
泛型约束
给泛型指定一些约束规范
如泛型无法识别自身是否有length属性,可以定义一个定义了length属性的接口,并由泛型继承
interface Lengthwise {
length: number
}
function fn <T extends Lengthwise>(x: T): void {
console.log(x.length)
}