介绍:
TypeScript 是JavaScript的超集,可以理解为进化版的 JavaScript。作为超集TypeScript 紧跟 ECMAScript 标准,所以 ES6/7/8/9 等新语法标准都是支持的,而且还在语言层面上,对一些语法进行拓展。比如新增了枚举(Enum)这种在一些语言中常见的数据类型,对类(Class)实现了一些ES6标准中没有确定的语法标准等等。还拥有强大的类型系统在编写代码的时候,就能检测出错误
安装:
npm install -g typescript
一.原始数据类型
//1.数值类型number const a: number = 3; //2.null类型 let k: null= null; //3.字符串类型string const b: string = "1"; //4.undefined类型 let u: undefined = undefined; //5.布尔类型boolean const g: boolean = true; //6.数组Array<type>或type[] 1.const list1: number[] = [1, 2, 3]; 2.const list2: Array<number> = [1, 2, 3]; //7.对象object const i: object = {};
二.TS中补充的六个类型
//1.元组类型:元组可以看做是数组的拓展,它表示已知数组中每一个位置上的元素的类型 let tuple: [string, number, boolean]; tuple = ["a", 2, false]; //上面定义了一个元组 tuple,它包含的三个元素类型是固定的string, number, boolean。 //当我们为 tuple 赋值时,各个位置上的元素类型都要对应,元素个数也要一致。 tuple[1] = 3; //通过索引给单个的元素赋值,tuple[1]的时候类型为number,所以赋值3是没问题的 //2.枚举enum类型:可以给一组数值赋予名字 enum Roles { SUPER_ADMIN, ADMIN, USER } //上面定义的枚举类型 Roles 里面有三个值,TypeScript 会为它们每个值分配编号, //默认从 0 开始,依次排列,所以它们对应的值是SUPER_ADMIN = 0,ADMIN = 1,USER = 2 const superAdmin = Roles.SUPER_ADMIN; console.log(superAdmin); // 0 //使用的时候,就可以使用名字而不需要记数字和名称的对照关系 enum Roles { SUPER_ADMIN = 11, ADMIN, USER } //可以为每个值都赋予不同的、不按顺序排列的值 //3.Any类型:即任意类型 let value: any; value = 123; value = "abc"; value = false; //我们定义变量 value,指定它的类型为 any,接下来赋予任何类型的值都是可以的 const array: any[] = [1, "a", true]; //还可以在定义数组类型时使用 any 来指定数组中的元素类型为任意类型 //4.void类型:表示没有任意类型,就是什么类型都不是,这在我们定义函数,函数没有返回值时会用到 const consoleText = (text: string): void => { console.log(text); }; //这个函数没有返回任何的值,所以它的返回类型为 void //void 类型的变量只能赋值为 undefined 和 null,其他类型不能赋值给 void 类型的变量 //5.never类型:指那些永不存在的值的类型 const errorFunc = (message: string): never => { throw new Error(message); }; //errorFunc 函数总是会抛出异常,所以它的返回值类型是 never,用来表明它的返回值是永不存在的 //6.unknown:表示未知的类型,另一种是 any let value: unknown; value = true; // OK value = 42; // OK value = "Hello World"; // OK value = []; // OK value = {}; // OK value = Math.random; // OK value = null; // OK value = undefined; // OK value = new TypeError(); // OK value = Symbol("type"); // OK //unknown 类型只能被赋值给 any 类型和 unknown 类型本身 let value1: unknown = value; // OK let value2: any = value; // OK
三.指定变量的类型
//这里指定person 参数类型为 string,所以user的值必须传string类型 function sayHello(person: string) { return 'Hello, ' + person; } let user = 'Tom'; console.log("指定变量的类型",sayHello(user));//Hello, Tom //判断参数类型 function sayHello(person: string) { if (typeof person === 'string') { return 'Hello, ' + person; } else { throw new Error('person is not a string'); } } let use = "111"; //use的值必须是string类型,否则报错 throw new Error('person is not a string'); console.log("判断参数类型",sayHello(use));
自动编译文件
如果直接使用tsc指令,则可以自动将当前项目下的所有ts文件编译为js文件。
但是能直接使用tsc命令的前提时,要先在项目根目录下创建一个ts的配置文件 tsconfig.json
tsconfig.json是一个JSON文件,添加配置文件后,只需只需 tsc 命令即可完成对整个项目的编译
具体配置:
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["vite/client"],
"paths": {
"@/*": ["./src/*"],
"/@/*": ["./src/*"]
}
},
"include": ["src/**/*"]
}
四.TypeScript 断言
// “尖括号” 语法 let someValue: any = "this is a string"; console.log(someValue); //this is a string let strLength: number = (<string>someValue).length; console.log(strLength); // 16 //as 语法 let someValue: any = "this is a string"; console.log(someValue);//this is a string let strLength: number = (someValue as string).length; console.log(strLength);//16
五.接口
接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约
//使用interface来定义接口 //例子: interface userInfo { name: string; age: number; address?: string; //问号代表该属性可添加可不添加 } function getUserInfo(u: userInfo) { console.log(u.name); //张三 console.log(u.age); //24 console.log(u.address); //北京 } let user1 = { name: "张三", age: 24, address: "北京" }; getUserInfo(user1);
//.只读属性(readonly):如果我们希望对象属性只能在对象刚创建的时候修改其值 //可以在属性名前制定readonly来指定只读属性 interface Point { readonly x: number; readonly y: number; } let p1: Point = { x: 10, y: 20 }; p1.x = 3; console.log("接口的只读属性", p1);//{x: 3, y: 20} //定义只读数组 let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray<number> = a; ro[0] = 12; //索引 a = ro; console.log("定义只读数组", a);//[12, 2, 3, 4]
//用接口定义函数的形状 //对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变 interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { return source.search(subString) !== -1; }
//数组类型:通过索引得到的类型,只能是number或者string interface StringArray { [index: number]: string; } let myArray: StringArray = ["Bob", "Fred"]; console.log(myArray);//['Bob', 'Fred'] let myStr: string = myArray[0]; console.log(myStr);//Bob
//类类型:就是一个类去实现接口,而不是直接把接口拿来用,写法就是 class implements interface interface IClock { /*定义了一个接口 这个接口中有一个属性和一个方法*/ currentTime: Date;//属性 getTime(d: Date);//方法 } /*Time类实现IClock接口*/ class Time implements IClock { currentTime: Date; getTime(d: Date) { this.currentTime = d; } }
六.函数的类型
// 函数声明(Function Declaration) function sum(x, y) { return x + y; } //函数 function 函数名(变量:类型,变量:类型):返回值类型 { //return a+b; // } function sum(x: number, y: number): number { return x + y; } sum(1, 2); // 函数表达式(Function Expression) let mySum = function (x, y) { return x + y; };
//可选参数:可选参数必须接在必需参数后面 function buildName(firstName: string, lastName?: string) { if (lastName) { return firstName + ' ' + lastName; } else { return firstName; } } let tomcat = buildName('Tom', 'Cat'); console.log(tomcat);//Tom Cat let tomtom = buildName('Tom'); console.log(tomtom);//Tom
七.声明文件:必需以 .d.ts
为后缀
declare var 声明全局变量 declare var jQuery: (selector: string) => any; jQuery('#foo'); declare function 声明全局方法 declare class 声明全局类 declare enum 声明全局枚举类型 declare namespace 声明(含有子属性的)全局对象 interface 和 type 声明全局类型 export 导出变量 export namespace 导出(含有子属性的)对象 export default ES6 默认导出 export = commonjs 导出模块 export as namespace UMD 库声明全局变量 declare global 扩展全局变量 declare module 扩展模块 /// <reference /> 三斜线指令
通常我们会把声明语句放到一个单独的文件(
jQuery.d.ts
)中,这就是声明文件
八.内置对象
//ECMAScript 标准提供的内置对象有:Boolean、Error、Date、RegExp,在TypeScript 中的定位为 let b: Boolean = new Boolean(1);//{true} let e: Error = new Error('Error occurred');//Error: Error occurred let d: Date = new Date();//中国标准时间 let r: RegExp = /[a-z]/;///[a-z]/ //DOM 和 BOM 提供的内置对象有:Document、HTMLElement、Event、NodeList let body: HTMLElement = document.body; let allDiv: NodeList = document.querySelectorAll('div'); document.addEventListener('click', function(e: MouseEvent) { // Do something });
九.类
- 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
- 对象(Object):类的实例,通过
new
生成- 面向对象(OOP)的三大特性:封装、继承、多态
- 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
- 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如
Cat
和Dog
都继承自Animal
,但是分别实现了自己的eat
方法。此时针对某一个实例,我们无需了解它是Cat
还是Dog
,就可以直接调用eat
方法,程序会自动判断出来应该如何执行eat
- 存取器(getter & setter):用以改变属性的读取和赋值行为
- 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如
public
表示公有属性或方法- 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
一.ES6 中类的用法
class Animal { public name; public constructor(name) { this.name = name; } } let ak = new Animal('Jack'); console.log(ak.name); // Jack ak.name = 'Tom'; console.log(ak.name); // Tom
使用
class
定义类,使用constructor
定义构造函数。通过
new
生成新实例的时候,会自动调用构造函数。
class Animal { public name; constructor(name) { this.name = name; } sayHi() { return `My name is ${this.name}`; } } let a = new Animal('Jack'); console.log(a.sayHi()); // My name is Jack
类的继承§
使用
extends
关键字实现继承,子类中使用super
关键字来调用父类的构造函数和方法。class Cat extends Animal { constructor(name) { super(name); // 调用父类的 constructor(name) console.log(this.name); } sayHi() { return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi() } } let c = new Cat('Tom'); // Tom console.log(c.sayHi()); // Meow, My name is Tom
存取器§
使用 getter 和 setter 可以改变属性的赋值和读取行为:
class Animal { constructor(name) { this.name = name; } get name() { return 'Jack'; } set name(value) { console.log('setter: ' + value); } } let a = new Animal('Kitty'); // setter: Kitty a.name = 'Tom'; // setter: Tom console.log(a.name); // Jack
静态方法§
使用
static
修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:class Animal { static isAnimal(a) { return a instanceof Animal; } } let a = new Animal('Jack'); Animal.isAnimal(a); // true a.isAnimal(a); // TypeError: a.isAnimal is not a function
二.TypeScript 中类的用法§
public private 和 protected§
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是
public
、private
和protected
。public
修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public
的private
修饰的属性或方法是私有的,不能在声明它的类的外部访问protected
修饰的属性或方法是受保护的,它和private
类似,区别是它在子类中也是允许被访问的class Animal { public name; public constructor(name) { this.name = name; } } let a = new Animal('Jack'); console.log(a.name); // Jack a.name = 'Tom'; console.log(a.name); // Tom
name
被设置为了public
,所以直接访问实例的name
属性是允许的。很多时候,我们希望有的属性是无法直接存取的,这时候就可以用
private
了:class Animal { private name; public constructor(name) { this.name = name; } } let a = new Animal('Jack'); console.log(a.name); a.name = 'Tom'; // index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'. // index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
TypeScript 编译之后的代码中,并没有限制
private
属性在外部的可访问性。上面的例子编译后的代码是:
var Animal = (function () { function Animal(name) { this.name = name; } return Animal; })(); var a = new Animal('Jack'); console.log(a.name); a.name = 'Tom';
使用
private
修饰的属性或方法,在子类中也是不允许访问的:class Animal { private name; public constructor(name) { this.name = name; } } class Cat extends Animal { constructor(name) { super(name); console.log(this.name); } } // index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
用
protected
修饰,则允许在子类中访问class Animal { protected name; public constructor(name) { this.name = name; } } class Cat extends Animal { constructor(name) { super(name); console.log(this.name); } }
给类加上 TypeScript 的类型
class Animal { name: string; constructor(name: string) { this.name = name; } sayHi(): string { return `My name is ${this.name}`; } } let a: Animal = new Animal('Jack'); console.log(a.sayHi()); // My name is Jack
类与接口
接口(Interfaces)的另一个用途,对类的一部分行为进行抽象。
类实现接口§
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用
implements
关键字来实现。这个特性大大提高了面向对象的灵活性。举例来说,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它:
interface Alarm { alert(): void; } class Door { } class SecurityDoor extends Door implements Alarm { alert() { console.log('SecurityDoor alert'); } } class Car implements Alarm { alert() { console.log('Car alert'); } }
一个类可以实现多个接口:
interface Alarm { alert(): void; } interface Light { lightOn(): void; lightOff(): void; } class Car implements Alarm, Light { alert() { console.log('Car alert'); } lightOn() { console.log('Car light on'); } lightOff() { console.log('Car light off'); } }
上例中,
Car
实现了Alarm
和Light
接口,既能报警,也能开关车灯。接口继承接口§
接口与接口之间可以是继承关系:
interface Alarm { alert(): void; } interface LightableAlarm extends Alarm { lightOn(): void; lightOff(): void; }
这很好理解,
LightableAlarm
继承了Alarm
,除了拥有alert
方法之外,还拥有两个新方法lightOn
和lightOff
。接口继承类§
常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript 中却是可以的:
class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } interface Point3d extends Point { z: number; } let point3d: Point3d = {x: 1, y: 2, z: 3};
为什么 TypeScript 会支持接口继承类呢?
实际上,当我们在声明
class Point
时,除了会创建一个名为Point
的类之外,同时也创建了一个名为Point
的类型(实例的类型)。所以我们既可以将
Point
当做一个类来用(使用new Point
创建它的实例):class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } const p = new Point(1, 2);
也可以将
Point
当做一个类型来用(使用: Point
表示参数的类型):class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } function printPoint(p: Point) { console.log(p.x, p.y); } printPoint(new Point(1, 2));
这个例子实际上可以等价于:
class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } interface PointInstanceType { x: number; y: number; } function printPoint(p: PointInstanceType) { console.log(p.x, p.y); } printPoint(new Point(1, 2));
上例中我们新声明的
PointInstanceType
类型,与声明class Point
时创建的Point
类型是等价的。所以回到
Point3d
的例子中,我们就能很容易的理解为什么 TypeScript 会支持接口继承类了:class Point { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } interface PointInstanceType { x: number; y: number; } // 等价于 interface Point3d extends PointInstanceType interface Point3d extends Point { z: number; } let point3d: Point3d = {x: 1, y: 2, z: 3};
当我们声明
interface Point3d extends Point
时,Point3d
继承的实际上是类Point
的实例的类型。换句话说,可以理解为定义了一个接口
Point3d
继承另一个接口PointInstanceType
。所以「接口继承类」和「接口继承接口」没有什么本质的区别。
值得注意的是,
PointInstanceType
相比于Point
,缺少了constructor
方法,这是因为声明Point
类时创建的Point
类型是不包含构造函数的。另外,除了构造函数是不包含的,静态属性或静态方法也是不包含的(实例的类型当然不应该包括构造函数、静态属性或静态方法)。换句话说,声明
Point
类时创建的Point
类型只包含其中的实例属性和实例方法:class Point { /** 静态属性,坐标系原点 */ static origin = new Point(0, 0); /** 静态方法,计算与原点距离 */ static distanceToOrigin(p: Point) { return Math.sqrt(p.x * p.x + p.y * p.y); } /** 实例属性,x 轴的值 */ x: number; /** 实例属性,y 轴的值 */ y: number; /** 构造函数 */ constructor(x: number, y: number) { this.x = x; this.y = y; } /** 实例方法,打印此点 */ printPoint() { console.log(this.x, this.y); } } interface PointInstanceType { x: number; y: number; printPoint(): void; } let p1: Point; let p2: PointInstanceType;
上例中最后的类型
Point
和类型PointInstanceType
是等价的。同样的,在接口继承类的时候,也只会继承它的实例属性和实例方法。
创建Vue3+ts项目:
node版本(14.x以上)、Vue-cli版本(4.x以上)
vue create 项目名--------选择自定义创建项目
光标到对应行,然后按空格即可选中需要的配置
项目目录: