前言
这篇blog是自己学习TS时的笔记,如果您想学习TS,发现官网不太友好,可以参考一下我这篇文章。
如果您想参考本篇文章的所有代码,请戳这里:实验代码
TypeScript概述
在前端开发领域有JavaScript语言,为什么还要有TypeScript?
TypeScript同JavaScript相比,最大的特点是强类型,支持静态和动态类型,和JavaScript不同,这种强类型相比弱类型,可以在编译期间发现并纠正错误,降低了试错的成本也提升了代码的规范性。
TS(TypeScript简称) 是 JS(JavaScript简称)的超集。
其实TS可以认为是在JS的基础上增加了类型系统和 一些新的特性和语法,在运行时,需要将TS编译成JS。
安装TypeScript和配置项目文件
全局安装TypeScript
npm install -g typescript
配置项目文件(tsconfig.json)
在项目根目录下运行tsc --init
,这个文件中可以设置一些配置项,方便我们针对不同的项目要求。
我们主要配置以下几项:
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
配置文件后,在执行命令行:tsc
后,会根据tsconfig.json中的配置将TS代码转化为JS代码。
基础类型
与JS一样,TS也提供了一些基本类型:数字,字符串,布尔值,Any,void , Null 和 Undefined。
数字,字符串,布尔值:
// 数字
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// 字符串
const hName: string = '胡三疯';
// 布尔
let isLet: boolean = true; // false / true
Any
any表示任何类型,当我们在编程过程中对于一个变量不确定什么类型数据时,可以使用Any
// Any
let anyVariable: any = 4
anyVariable = 'this is anyVariable';
void
void 表示没有任何类型,在某个函数没返回值需要使用void,对于变量作用不大,如果用于变量的话,只能赋值ndefined 和 null,建议不要对变量使用此种类型
// void
function consoleFun(): void {
console.log("this is no return");
}
let unusable: void = undefined; // undefined / null
Null 和 Undefined
在TS中,这两种类型用处不大,特别在严格模式下,他们只能赋值给他们本身的类型和 void。
let u: undefined = undefined;
let n: null = null;
Symbols
symbol 也是一种基础类型,只不过是ECMAScript 2015之后新出的基础类型
let sym = Symbol();
当我们使用symbol 时,编译器会报错,提示Symbol 这个找不到声明,为什吗那?
因为在tsconfig.json文件中,指定的编译后的JS是"target": "es5",
对于es5以前的内置对象所对应的声明(标准库)会有,但是es6+之后所对应的标准库,我们需要在lib属性中自己新增,注意如果我们要把所有使用的标准库都在这里重新写,因为会覆盖默认的所有标准库。
"lib": ["ES2015","DOM"]
枚举类型
枚举类型一般用于某个对象有固定的几个值。在JS中,是没枚举这个类型的,一般使用对象的方式模仿枚举。
当是数字枚举时,我们可以不指定具体的枚举值,第一个会被赋值为0,后面的依次累加;若指定第一个值,后边的值若不指定,也会依次累加;当定义字符串枚举时,我们只有老老实实的给每项赋值
enum status {
publish = 0,
Unpublished = 1
}
let blog = {
title:"我是胡三疯",
content: "胡三疯是个好孩子。。。。。。",
postStatus: status.Unpublished
}
上边枚举在编译成JS对象时会被编译成双向键值对对象,也叫反向映射。如若不想被编译成这样,可以在enum前加上const,会被编译成对象
const enum status {
publish = 0,
Unpublished = 1
}
Object 类型
Object 类型并不单指对象类型,是指除了原始类型外的所有类型;我们定义对象类型的时候可以使用一种类似对象字面量的一种语法,但是对于对象类型的使用建议使用接口的方式,更专业
let obj1: Object = function(){}// {name: "胡三疯"} []
let obj2:{name: string, age: number} = {name: "胡三疯", age:18}
export {}
数组类型
对于数组类型的定义方式有两种:
- Array 泛型
- 元素类型 []
// 1. Array泛型
let arr1: Array<number> = [1, 4, 18]
// 2. 元素类型[]
let arr2: number[] = [1, 5, 12, 15]
对于元素数组指定好具体的类型,可以让我们在程序中省去对元素类型的判断
元组类型
元组类型是一种明确元素数量和类型的数组,是一种混合元素类型的数组,使用数组字面量的方式定义。
let tuple: [number, string] = [18, "胡三疯"]
函数类型
在定义函数时,需要制定参数的类型,返回值的类型可有可无,因为函数能够推断出返回值的类型
在定义函数时,有两种方式:
- 函数声明式
- 函数表达式
// 1. 函数声明
function add1(x:number, y:number):number {
return x + y;
}
// 2. 函数表达式
const add2 = function(x:number, y:number):number {
return x + y;
}
TypeScript里的每个函数参数都是必须的。若使某个参数变成可选参数,可以在参数名旁使用 ?实现可选参数的功能;也可以给某个参数设置默认值。
// 可选参数
function add3(x:number, y?:number):number {
y = y ? y :0
return x + y;
}
// 默认值
function add4(x:number, y:number = 0):number {
return x + y;
}
隐式类型推断
当我们声明一个变量时,如果没有表明类型,TS会根据变量值来判断变量的类型。这样有时间虽然可以简化我们的代码,但是使用时不建议这样使用。
// age 会被推断为 number
let age = 18;
// foo 会被推断为 any
let foo;
foo = 100
foo = "胡三疯"
类型断言
在我们程序中,若在类型推断中不能确定具体类型,在我们进行下步操作时,编译器肯定会报错。这时如果我们能够明确知道是什么类型的数据,我们就可以断言数据类型,减少处理的操作。
类型断言可以通过以下两种方式解决:
- as
- <数据类型>
let numArr = [100, 120, 150]
// 类型系统判断 num 是number 或 undefined
const num = numArr.find(i => i > 100 )
// const square = num * num; // num is possibly 'undefined'
// 类型断言
const num1 = num as number
const num2 = <number> num
const square1 = num1 * num1;
const square2 = num2 * num2;
接口
接口是一种规范,经常用于定义对象类型的变量。
interface Person {
name: string;
age: number
}
let sanFengHu: Person = {
name: "胡三疯",
age: 18
}
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。如:hobby
interface Person {
name: string;
age: number;
hobby ?: string
}
只读属性
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly来指定只读属性:
interface Person {
name: string;
age: number;
hobby ?: string
readonly id: string
}
let sanFengHu: Person = {
name: "胡三疯",
age: 18,
id:"411402199908806666"
}
动态成员
在接口定义时,我们不确定有什么属性可以使用动态成员
// 动态成员
interface dynamicProperty {
[property: string]: string
}
let dynamicObj: dynamicProperty = {
key1: "value1",
key2: "value2",
}
类
在ES6之前,JS是通过函数和原型继承的方式来实现。在ES6+之后,使用class来实现,在TS中也是使用Class来实现。
class Person {
name:string;
age:number;
constructor(name: string, age:number) {
this.name = name;
this.age = age
}
say(){
console.log("my name is" + this.name +",age" + this.age)
}
}
let person = new Person('胡三疯', 18);
person.say();
属性修饰符
针对对象中的属性,有几种修饰符来限定属性的访问范围:
- public 默认属性,可以自由的访问程序里定义的成员
- private 能在声明它的类的外部访问
- protected 与private功能类似,但是protected成员在派生类中仍然可以访问
// private 栗子
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // 错误: 'name' 是私有的.
// protected Vs private
class Person {
protected name: string;
private age: number;
constructor(name: string, age:number) { this.name = name; this.age = age}
}
class Employee extends Person {
constructor(name: string, age: number) {
super(name,age)
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.age}.`;
}
}
let howard = new Employee("Howard",18);
console.log(howard.getElevatorPitch());
readonly只读属性
只读属性只能在声明或构造函数里被初始化
class Person {
name:string;
readonly gender:string;
constructor(name: string, gender:string) {
this.name = name
this.gender = gender
}
}
let sanfeng_hu = new Person("sanfeng_hu", "man");
sanfeng_hu.gender = "man" // Cannot assign to 'gender' because it is a read-only property.
抽象类
对于一些顶层的类我们可以作为抽象类,比如 将动物作为抽象类,然后小猫,小狗…作为具体类,在抽象类实现的方法,子类都可以继承,同时也可以定义一些抽象方法在子类中实现。
定义抽象类和抽象方法只需要在类名前加abstract即可。
// 抽象类
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
// 子类
class Dog extends Animal {
// 抽象方法实现
makeSound():void {
console.log("----makeSound++++++++")
}
}
let dog = new Dog()
dog.move()
dog.makeSound()
export {}
类和接口
在一些类中有共同的方法,但是具体实现不同时,我们可以定义一个接口规定这些类必须的方法,具体实现放到具体类中
export {}
interface runAndEat {
eat():void;
run():void;
}
class Dog implements runAndEat {
eat():void {
console.log("dog---eat");
}
run():void {
console.log("dog---run");
}
}
class Pig implements runAndEat {
eat():void {
console.log("Pig---eat");
}
run():void {
console.log("Pig---run");
}
}
let dog = new Dog()
let pig = new Pig()
dog.eat()
dog.run()
pig.eat()
pig.run()
泛型
泛型是指函数在定义时不确定具体类型,等到具体调用时再确定,并且可以保证传入类型和传出类型是同一类型。
// 数字数组
function createNumArr(length:number, item: number):number[] {
return Array(length).fill(item)
}
// 字符串数组
function createStrArr(length:number, item: string):string[] {
return Array(length).fill(item)
}
// 使用泛型
function createArr<T>(length:number, item: T):T[] {
return Array(length).fill(item)
}
let numArr1 = createNumArr(2, 6);
let strArr1 = createStrArr(2, "hh")
let numArr2 = createArr(2, 7)
let strArr2 = createArr(2, "dd")
console.log(numArr1,strArr1,numArr2,strArr2)
export {}
TypeScript作用域问题
在我们对每个特性做练习的时候,会出现相同变量名的情况,因为这些变量是在全局作用域下,所以会报重复命名的错误,那么我们只需要将每个文件放在单独作用域内就行了,可以使用立即执行函数或 ES Modules
let sym1 = Symbol();
export {}
在这里我使用 ES Modules,只需要在文件中加上:export {}