文章目录
TypeScript
优势:
- 程序更容易理解(如在函数传入参数的时候限制传入类型)
- 效率更高
- 更少的错误
- 更好的包容性
安装使用
npm install -g typescript
可在cmd输入tsc
来查看是否安装成功,安装成功则有反应
可通过tsc +ts文件名
将ts文件编译成js文件
要在vscode的run code
中直接运行ts文件,需要全局安装ts-node
npm install -g ts-node
数据类型
原始类型:
- Boolean
- Null
- Undefined
- Number
- BigInt
- String
- Symbol
引用类型:
- Object
声明类型的方式
使用:
声明数据类型
变量值为 null 和 undefined 可以匹配任意类型(相当于这些其他类型的共同子类)
any 类型可以匹配任意的数据类型,可以修改为任意数据类型,将一个 any 类型的变量传递任何数据类型结果都是 any 类型
联合类型:使用 |
将类型规则隔开,表示该变量的值可为这些类型
类型推论:当声明变量并赋值没有指明类型时,会自动根据第一次赋值类型给上对应数据类型,后面再更改为其他数据类型时也会报错
let isDone: boolean = false;
function (prams:any) {
...
}
let unite: number | string | boolean = 'string';
数组
let arrOfNumber: number[] = [1, 2, 3, 4];
元组:若想按顺序规定数组内的元素类型时,使用元组
但是不能写出长度
let user: [string, number] = ['string', 1];
let user_: [string, number] = ['string', 1, 'string']; // 报错
函数
?
可以表示函数的可选属性,但是非可选的参数不能排在可选参数后声明,当给函数参数设定默认值的时候也可表示可选
function add(x: number, y: number = 10, z?: number) {
if (typeof z === 'number') {
return x + y + z
}
else {
return x + y
}
}
声明返回值类型
声明为any或void时可以无返回值,如果不声明则使用第一次声明函数时的返回值作为规则
function add(): number {
...
}
类
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
say() {
console.log(`my name is ${this.name}`);
}
}
const lily = new Animal('lily');
lily.say();
继承
和其他语言的继承差不多,能使用父类方法,也能重写父类的属性方法
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
say() {
console.log(`my name is ${this.name}`);
}
}
class Cat extends Animal {
eat() {
console.log(`I am eating`);
}
}
const susie = new Cat('susie');
susie.say();
susie.eat();
重写方法:注意的是如果重写了父类方法,需要使用父类方法时,需要在构造函数中调用super方法,并在后续中用super去执行父类方法
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
say() {
console.log(`my name is ${this.name}`);
}
}
class Cat extends Animal {
constructor(name) {
super(name); // 如果父类构造函数需传入参数,则constructor和super也应该传入参数
}
say() {
console.log(`I am saying`);
}
animalSay() {
super.say();
}
}
const susi = new Cat('susie');
susi.say(); // I am saying
susi.animalSay(); // my name is susie
公开与私有
- public:类成员可以被父子实例和子类访问到
- private:类成员不能被父子实例与子类访问到
- protected:类成员可以被子类访问到但是不能被父子实例访问到
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
public say() {
console.log(`my name is ${this.name}`);
}
private run() {
console.log('I am runing');
}
}
const cat = new Animal('cat');
cat.say()
cat.run() // 报错
静态
class Animal {
static say() {
console.log(`hello`);
}
}
Animal.say()
静态方法内只能使用类的静态成员,不能使用实例成员
class Cat {
age: number = 12;
static food = 'fish';
run() {
this.age = 13;
}
static say() {
this.age = 14; // 报错,不能使用实例成员
this.food = 'can';
}
}
接口
- 对对象的形状(shape)进行描述
- 对类(class)进行抽象
- 鸭子类型(模糊推断判断,更关注对象能否使用而非对象的类型本身)
- 接口内的方法不能实现,即只能拥有函数声明而不能拥有函数体
- 一个类只能继承一个类,但可接收多个接口并实现
规定对象
由于接口实例必须实现接口的全部内容,因此可以方便定义对象属性类型
interface Person {
name: string;
age: number;
}
let mary: Person = {
name: 'mary',
age: 12
}
实现接口属性不能多也不能少,如果有些属性是非必须实现的,应该使用 ?
表示该属性为可选属性
interface Person {
name: string;
age?: number;
}
let mary: Person = {
name: 'mary',
}
只读属性:用 readonly
声明,只能在实现接口的时候赋值,后面更改赋值会报错
interface Person {
readonly name: string;
age?: number;
}
let mary: Person = {
name: 'mary',
}
mary.name = 'jack'; // 报错
配合类
方便灵活,规定约束了具体哪些类中必须实现的哪些功能
interface Radio {
switchRadio():void;
}
interface Battery {
checkBatteryStatus();
}
class Car implements Radio {
switchRadio() {
console.log('实现Car的switchRadio');
}
}
class Cellphone implements Radio, Battery {
switchRadio() {
console.log('实现Cellphone的switchRadio');
}
checkBatteryStatus() {
console.log('实现Cellphone的checkBatteryStatus');
}
}
枚举
枚举本质上是用一串有顺序的 int 数值去依次表示枚举里的内容
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction.Up) // 0
console.log(Direction.Down) // 1
console.log(Direction.Left) // 2
console.log(Direction.Right) // 3
console.log(Direction[0]) // Up
console.log(Direction[1]) // Down
console.log(Direction[2]) // Left
console.log(Direction[3]) // Right
当给具体某个属性赋值时,后面的属性会依据这个赋值数字往下递增赋值
enum Direction {
Up,
Down = 6,
Left,
Right
}
console.log(Direction.Up) // 0
console.log(Direction.Down) // 6
console.log(Direction.Left) // 7
console.log(Direction.Right) // 8
枚举实现
enum Direction {
Up,
Down,
Left,
Right
}
// 等同于下面的js代码
var Direction;
(function (Direction) {
// 将Direction对象新增一个属性up,并赋值0,然后再新增一个属性0 并赋值up
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
/*
最终Direction对象的结果:
{
'0': 'Up',
'1': 'Down',
'2': 'Left',
'3': 'Right',
Up: 0,
Down: 1,
Left: 2,
Right: 3
}
*/
常量枚举
常量枚举仅可在属性、索引访问表达式、导入声明的右侧、导出分配或类型查询中使用
可提升枚举性能
enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
var value = 'Up';
if(value === Direction.Up) {
console.log('do Up')
}
// 转换
var Direction;
(function (Direction) {
Direction["Up"] = "Up";
Direction["Down"] = "Down";
Direction["Left"] = "Left";
Direction["Right"] = "Right";
})(Direction || (Direction = {}));
var value = 'Up';
if (value === Direction.Up) {
console.log('do Up');
}
const enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
var value = 'Up';
if(value === Direction.Up) {
console.log('do Up')
}
// 转换
var value = 'Up';
if (value === "Up" /* Up */) {
console.log('do Up');
}
泛型
在使用函数或类时,声明时不指定属性的类型,使用的时候再声明属性类型
function echo<T>(arg: T): T { // 变量名是任意的,但是普遍使用 T 来表示泛型
return arg;
}
根据在实际调用的时候传入的值来判断返回类型
const result = echo(true); // 此时该函数的返回值为 boolean 类型
const result = echo('str'); // 此时该函数的返回值为 String 类型
如果不使用泛型,则想让一个函数能匹配任意类型的数据,则会返回 any 类型
function echo(arg) {
return arg;
}
const result1 = echo(true); // 函数返回值类型为 any
const result2 = echo('str'); // 函数返回值类型为 any
这样的坏处是对返回值没有数据类型的约束,可以随便更改其类型
如果使用泛型,由于已经限制了类型,因此更改返回数据的类型会报错
function echo(arg) {
return arg;
}
var result = echo(true);
result = 'srt'; // 不会报错
泛型元组
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
const result = swap([true, 'srt']) // result 的类型为 [String, Boolean]
约束泛型
限制一部分传入泛型的类型
function echoWithArr<T>(arg: T): T {
console.log(arg.length); // 由于不是所有的类型都具有 length 属性,因此那么写会报错
return arg;
}
简易改写:
即限制了传入即返回值必须为一个数组类型,数组里的元素是泛型可任意
function echoWithArr<T>(arg: T[]): T[] {
console.log(arg.length)
return arg
}
但是这样写很不方便,因此使用约束泛型的写法,由于继承接口,接口内的成员必须被实现,因此实际上限制了传入参数 arg 必须要拥有 length 且数据类型为 number 才可被传入
这是接口的鸭子类型特性的一种应用
interface IwithLength {
length: number
}
function echoWithLength<T extends IwithLength>(arg: T): T {
console.log(arg.length)
return arg
}
类、接口配合泛型
类
class Queue<T> {
private data = [];
push(item: T) {
return this.data.push(item);
}
pop(): T {
return this.data.pop()
}
}
// 在实例化的时候声明类的泛型所指数据类型
const queue1 = new Queue<number>()
const queue2 = new Queue<boolean>()
queue1.push(3)
queue1.push(true) // 报错
接口
interface keyPair<T, U> {
key: T;
value: U;
}
let kp1: keyPair<number, string> = {
key: 123,
value: 'str'
}
let kp2: keyPair<boolean, number> = {
key: true,
value: 123
}
规范函数
interface IPlus<T> {
(a: T, b: T): T
}
function plus(a: number, b: number): number {
return a + b;
}
function connect(a: string, b: string): string {
return a + b;
}
const funPlus: IPlus<number> = plus;
const funConnect: IPlus<string> = connect;
// 如果不用泛型接口,想要规范的给变量赋予函数时,应该这样写,使用泛型接口可以省了很多代码
const funPlus: (a: string, b: string) => string = plus;
操作类型
类型别名
使用 type
声明要注明的类型别名
type PlusType = (x: number, y: number) => number;
function sum(x: number, y: number): number {
return x + y;
}
const sum_: PlusType = sum;
// 等同于
const sum_: (x: number, y: number) => number = sum;
联合别名
type NameResolver = () => string;
type NameOrResolver = string | NameResolver
// 判断传入的类型是一个返回值为string的方法还是一个字符串
function getName(n: NameOrResolver): string {
if(typeof n === 'string') {
return n
}else {
return n()
}
}
类型断言
当编译器不清楚数据的类型时,会默认采取类型推论的策略给对应代码添加上数据类型,如果自己本身比编译器更清楚代码应该拥有的数据类型,则使用类型断言去覆盖编译器的类型推论,即编译器不清楚数据类型时,会直接采用自定义的类型断言,而非去推论类型
// 由于类型是string或者number,因此编译器无法确定input是否会有length属性,number 则无
function getLength(input: string | number): number {
const str = input
if(str.length) { // 这里会报错
return str.length
}else {
const number = input
return number.toString().length // 这里不会,因为str也可以有toString()方法
}
}
使用类型断言,关键字 as
且数据类型是类(大写开头)
function getLength(input: string | number): number {
const str = input as String // 先将不确定的input断言为string类型进行操作,防止后面的代码报错
if(str.length) {
return str.length
}else {
const number = input as Number
return number.toString().length
}
}
断言的快捷写法:在变量前加上 <类型>
,注意这里的类型又是小写了
function getLength(input: string | number): number {
if((<string>input).length) {
return (<string>input).length
}else {
return (<number>input).toString().length
}
}
类型断言并非类型转换,它不能推断为非法的类型
function getLength(input: string | number): boolean {
return (<boolean>input) // 报错,
}
命名空间
内部模块称作命名空间,外部模块则简称为模块
模块和命名空间
命名空间
- 在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内
- 不同文件下同一个命名空间内的变量不能同名,但是这不代表可以直接使用另外一个文件的代码
- 本质上是闭包,用来隔离作用域
// namespace中可以定义任意多的变量,这些变量只能在shape下可见,如果要在全局内可见的话就要使用export关键字,将其导出
namespace Shape {
const pi = Math.PI
export function cricle(r: number) {
return pi * r ** 2
}
}
- 一个文件下的内容,如果想要另外一个文件能访问到这部分代码,应该使用
export
导出到外部空间(无论是否同一个命名空间)
如果AB文件同属于一个命名空间,当 B 想要引用 A 方数据的时候,应该使用 ///<reference path='A.ts' />
// A.ts
namespace Demo {
export interface A {
foo()
}
}
// B.ts
///<reference path="A.ts"/>
namespace Demo {
class B implements A {
foo() {
console.log('实现A接口的foo方法')
}
}
const b = new B()
b.foo() // 实现A接口的foo方法
}
如果AB文件不属于同一个命名空间,不仅要使用 ///<reference path='A.ts' />
引入文件,还要注明在导出的对象前加上命名空间去使用
// A.ts
namespace Demo_A {
export interface A {
foo()
}
}
// B.ts
///<reference path="A.ts"/>
namespace Demo_B {
class B implements Demo_A.A {
foo() {
console.log('实现A接口的foo方法')
}
}
const b = new B()
b.foo() // 实现A接口的foo方法
}
模块
- 模块在其自身的作用域里执行,而不是在全局作用域里,意味着定义在一个模块里的变量,函数,类等只有引入模块以后才能使用
- 只要在文件中使用了 import 和 export 语法,就被编译器视为一个模块
- 一个完整功能的封装,对外提供的是一个具有完整功能的功能包
- 一个模块里可能会有多个命名空间
- 导出声明:任何声明都能通过添加 export 关键字来导出(如:变量,函数,类,类型别名,或接口)
为了支持CommonJS和AMD的exports, TypeScript提供了export =语法
export = 语法定义一个模块的导出对象(这里对象是指:类,接口,命名空间,函数或枚举)
若使用export = 导出一个模块,则必须使用TypeScript的特定语法import module = require("module")来导入此模块
class Demo {
...
}
export = Demo;
import demo = require("./Demo");
- 模块的一个特点是不同的模块永远也不会在相同的作用域内使用相同的名字
- 不应该对模块使用命名空间,使用命名空间是为了提供逻辑分组和避免命名冲突,模块文件本身已经是一个逻辑分组,并且它的名字是由导入这个模块的代码指定,所以没有必要为导出的对象增加额外的模块层