TypeScript学习笔记
TypeScript学习笔记
TypeScript的快速上手
一、初识TypeScript
TypeScript是JavaScript的超集,最终会被编译城JavaScript代码
TypeScript作者是安德斯·海尔斯伯格,c#的首席架构师,是开源和跨平台的编程语言
TypeScript扩展了JavaScript的语法,是为大型应用的开发而设计
二、安装TypeScript
npm install -g typescript
三、VScode自动编译TypeScript
- 生成配置文件
tsc --init
- 在tsconfig.json文件中修改配置
key | value |
---|---|
outDir : | 这里是js代码的输出目录 |
strict : | false(不使用严格模式) |
- 启动监视任务
终端 >> 运行任务 >> 监视tsconfig.json
TS常用语法
一、基础类型
布尔值
boolean:true/false
let flag:boolean = true
console.log(flag)
数字类型
number
let num:number = 100
console.log(num)
字符串
string
let str:string = '小明很帅'
console.log(str)
undefined和null
let u: undefined = undefined
let n: null = null
默认情况下undefined和null是所有类型的子类型,这意味着可以将undefined和null赋值给任意类型的变量。
数组
定义数组共有两种方式
方式一:可以在 元素类型后面接上[]
,表示由这种元素类型构成的数组
let arr: number[] = [ 1, 2, 3, 4 ]
方式二:使用数组泛型 Array<number/string>
其中<>内为数据的元素类型
let arr: Array<number> = [ 1, 2, 3, 4 ]
元组(Tuple)
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同,例如:
let tup:[ number, string, boolean ] = [ 100, '小明好帅', true ]
枚举(enumeration)
enum枚举类型是对JavaScript类型的一个补充,可以为一组数据赋予一个名字
enum Player{
Kobe,
James,
Iverson
}
//默认情况下,枚举数值从0开始,依次递增,可以理解为数组的index
//当然,我们也可以手动指定元素的编号
enum Player{
Iverson = 3,
James = 23,
Kobe = 24
}
let num: Player = Player.Kobe
console.log(num) //24
//当我们在枚举元素中间指定元素编号时,其后的元素也会根据当前指定的编号依次递增
enum Player{
Iverson,
James = 23,
Kobe
}
let num1: Player = Player.Iverson
let num2: Player = Player.James
let num3: Player = Player.Kobe
console.log(num1, num2, num3) //0, 23, 24
//我们也可以根据指定的编号获取到对应的元素
enum Player{
Kobe,
James,
Iverson
}
let playerName:string = Player[2]
console.log(playerName) //Iverson
any(表示可以为任意类型)
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any 类型来标记这些变量:
let unKnown: any = 123
unKnown = '小明很帅'
unKnown = true
或者在我们定义一个由非同一元素类型组成的数组时,也可以用any类型
let arr:any[] = [ 123, '小明很帅', true ]
arr[1] = undefined
void(表示没有任何类型)
如一个函数如果没有返回值时,其返回值类型就可以为void
function nothing(str: string):void{
console.log('我执行完毕,并且不需要返回值')
}
object
object 表示非原始类型,也就是除 number,string,boolean之外的类型。
使用 object 类型,就可以更好的表示像 Object.create 这样的 API。例如:
function fn (obj: object):object{
console.log(obj)
return {
name:'kobe',
num:24
}
}
fn(123) //error
fn([1, 2, 3])
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种
需求1: 定义一个一个函数得到一个数字或字符串值的字符串形式值
function fn(bar:number | string):string{
if (typeof bar === 'srting') return bar
if (typeof bar === 'number') {
return bar.toString()
}
}
类型断言
类型断言就是在编译的过程中不对传递的参数进行类型判断,假设,在传递参数时程序员已经对该参数的类型进行预先的检查。
类型断言由两种方式:
方式一 (<类型>元素)
方式二 (元素 as 类型)
function fn(bar:string | number):number{
if((<string>bar).length) { //方式一
return (bar as string).length //方式二
}else{
return bar.toString().length
}
}
类型推断
在变量赋值时,默认当前值的类型为元素类型
let num = 123 //此处将推断变量num类型为number
num = '123' //error
在声明变量时,如果没有对变量进行赋值,将推断变量元素类型为any
let unKnown
unKnown = 123
unKnown = '小明很帅'
unKnown = true
二、接口(interface)
初探interface
TypeScript的核心原则之一是对值所具有的结构进行类型检查,我们使用接口来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)
是一种约束,一种限制,一种规则
interface IStudent {
readonly name:string //readonly表示只读,不可在对象外部对只读属性的值进行修改
age:number
gender:string
grade?:number //?表示为可选属性,构造对象时可填可不填,非必须项
}
const xiaoMing :IStudent = {
name:'xiaoming',
age:12,
gender:'male',
grade:5
}
const xiaoHong :IStudent = {
name:'xiaohong',
age:11,
gender:'female'
}
只读属性:readonly添加在接口的属性名前
可选属性:将 ?符号添加在接口的属性名后
函数类型
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
//需求:定义一个求两个 数值 和的函数
interface Isum{
(num1:number,num2:number):number
}
const sum: Isum = function(num1: number, num2: number):number{
return num1 + num2
}
类类型
TypeScript 可以强制一个类来符合定义好的规则
类实现接口时利用关键字implements(实现)
interface resolve {
resolve():object
}
interface reject {
reject():object
}
class MyPromise implements resolve,reject { //一个类可以实现多个接口
resolve(){
return {
promiseState:'fulfilled',
promiseResult:'value'
}
}
reject(){
return {
promiseState:'reject',
promiseResult:'reason'
}
}
}
const p1 = new MyPromise()
console.log(p1.resolve());
同时类类型接口也可以继承,利用关键字extends
interface resolve {
resolve():object
}
interface reject {
reject():object
}
interface Ipromise extends resolve, reject{
}
class MyPromise implements Ipromise { //一个类可以实现多个接口
resolve(){
return {
promiseState:'fulfilled',
promiseResult:'value'
}
}
reject(){
return {
promiseState:'reject',
promiseResult:'reason'
}
}
}
const p1 = new MyPromise()
console.log(p1.resolve());
三、类
对于传统的JavaScript程序,我们会使用 函数 或者 基于原型链继承 来创建可重复使用的组件,在ES6新增语法中,class提供了面向对象编程的更好的解决思路,而在TypeScript中,同样支持class的使用
基本示例
class Car {
//声明属性
carName:string
price:number
//构造方法
constructor(carName:string,price:number){
this.carName = carName
this.price = price
}
//一般方法(相当于Car.prototype上写的方法,实例对象可以直接调用)
showCarName():string{
return this.carName + '这个车真的很不错,他的售价是' + this.price
}
}
const BWM = new Car('BWM',298000)
console.log(BWM.showCarName());
我们使用new关键字创建了一个class Car的实例,会调用class Car中的构造函数(constructor)来构建实例对象,并进行初始化。
继承
在 TypeScript 里,我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。
class Person {
name:string
constructor(name:string){
this.name = name
}
sayHi():string{
return '你好啊,我叫' + this.name
}
}
class Student extends Person{
hobby:string
constructor(name:string,hobby:string){
super(name) //在派生类的构造函数中必须先调用super方法
this.hobby = hobby
}
showMyHobby():string{
return '我的爱好是' + this.hobby
}
}
const stu1 = new Student('小明','打篮球')
console.log(stu1.name); //小明
console.log(stu1.sayHi()); //你好啊,我叫小明
console.log(stu1.showMyHobby()); //我的爱好是打篮球
这里展示了类通过 extends关键字 从基类继承了属性和方法,Student是一个派生类,派生自Person这个基类,我们也称Person为超类,Student为子类。
因为Student继承自Person,所以Student构建的实例stu1也可以访问超类中定义的属性、调用超类中的方法。
我们再看接下来的示例:
class Person {
name:string
constructor(name:string){
this.name = name
}
sayHi(msg:string = '你好啊'){
console.log(msg + ',我是' + this.name);
}
}
class Student extends Person{
constructor(name:string){
super(name) //在派生类的构造函数中必须先调用super方法
}
sayHi(msg:string = '老师你好'){
super.sayHi(msg)
}
showMyHobby(hob:string){
console.log('我的爱好是' + hob);
}
}
class Teacher extends Person{
constructor(name:string){
super(name)
}
sayHi(msg:string = '同学你好'){
super.sayHi(msg)
}
}
const stu1 = new Student('小明')
stu1.sayHi() //老师你好,我是小明
stu1.showMyHobby('打篮球') //我的爱好是打篮球
const tea1 = new Teacher('李老师')
tea1.sayHi() //同学你好,我是李老师
//父类型的引用指向子类型的实例,这种情况就叫做多态
const stu2: Person = new Student('小刚')
stu2.sayHi('李老师你好') //李老师你好,我是小刚
//当子类型中没有扩展的方法时,允许子类型的引用指向父类型的实例
//即子类型中的方法与父类型的方法一一对应
const tea2: Teacher = new Person('张校长')
tea2.sayHi() //你好啊,我是张校长
//当子类型中存在扩展方法时,不允许子类型的引用指向父类型的实例,会编译失败
//类型 "Person" 中缺少属性 "showMyHobby",但类型 "Student" 中需要该属性
//'showMyHobby' is declared here.
const stu3:Student = new Person('小红')
stu3.sayHi()
在派生类中,如果不需要初始化其他属性,那么构造函数可写可不写,但如果添加构造函数,那么必须通过 super() 来调用超类的构造方法,并且 super() 要在构造函数内访问 this 属性之前。
公有、私有、受保护修饰符
公有public :关键字public修饰的属性为公有属性,同时也时默认属性。
私有private :关键字privata修饰的属性为私有属性,私有属性仅在当前类内部可以访问
受保护protected :关键字protected修饰的属性为受保护的属性,仅在当前类和子类中可以访问
我们通过一段代码对这三个修饰符来进一步的了解:
class Person {
name:string
constructor(name:string){
this.name = name
}
showName(){
console.log(this.name);
}
}
class Husband extends Person{
protected sonName:string
//caseDough 意为 私房钱
private caseDough:number
constructor(name:string,sonName:string,caseDough){
super(name)
this.sonName = sonName
this.caseDough = caseDough
}
protected showSonName(){
console.log(this.sonName);
}
private showCaseDough(){
console.log(this.caseDough);
}
}
class Wife extends Husband{
tellMeTruth(){
console.log(this.sonName);
super.showSonName()
//属性“caseDough”为私有属性,只能在类“Husband”中访问。
console.log(this.caseDough);
//属性“showCaseDough”为私有属性,只能在类“Husband”中访问。
super.showCaseDough()
}
}
const jack = new Husband('jack','JR.jack',50)
console.log(jack.name);
jack.showName()
//属性“sonName”受保护,只能在类“Husband”及其子类中访问。
console.log(jack.sonName);
//属性“showSonName”受保护,只能在类“Husband”及其子类中访问。
jack.showSonName()
//属性“caseDough”为私有属性,只能在类“Husband”中访问。
console.log(jack.caseDough);
//属性“showCaseDough”为私有属性,只能在类“Husband”中访问。
jack.showCaseDough()
由此可见,我们不能在外部或子类中访问由 private 修饰的 caseDough 属性和 showCaseDough 方法,显而易见私房钱的存款时绝对不能告诉妻子和别人的。
而由 protected 修饰的 sonName 属性和 showSonName 方法仅可以在当前类和子类中访问到,毕竟jack和rose都是好莱坞的顶级流量,为了不被别人打扰他们孩子的成长,需要对儿子的信息进行保护。
readonly修饰符
由关键字readonly修饰的属性将被设置为只读属性,而只读属性仅能在声明时或构造函数里进行初始化。
class Person {
readonly name:string = 'personX'
constructor(name: string){
this.name = name
}
}
const jack = new Person('jack')
//无法分配到 "name" ,因为它是只读属性。
jack.name = 'Li Goudan'
参数属性
在上面的例子中,我们必须在 Person 类里定义一个只读成员 name 和一个参数为 name 的构造函数,并且立刻将 name 的值赋给 this.name,这种情况经常会遇到。 参数属性可以方便地让我们在一个地方定义并初始化一个成员。 下面的例子是对之前 Person 类的修改版,使用了参数属性:
class Person {
constructor(readonly name: string){
}
}
const jack = new Person('jack')
console.log(jack.name); //jack
除了readonly以外,当以public、private、protected来修饰参数属性时,也会创建一个相应的属性。
存取器
TypeScript支持通过 getters/setters 来截取对对象成员的访问。
下面我们通过一个例子来了解以下:
class Player {
firstName: string
lastName: string
constructor(firstName:string = 'jack',lastName?:string){
this.firstName = firstName
this.lastName = lastName
}
get fullName (){
if(this.lastName) return this.firstName + ' ' + this.lastName
return this.firstName
}
set fullName (value){
const names = value.split(' ')
this.firstName = names[0]
this.lastName = names[1]
}
}
const player1 = new Player()
console.log(player1.fullName); //jack
const player2 = new Player('Allen')
console.log(player2.fullName); //Allen
const player3 = new Player('Aller','Iverson')
console.log(player3.fullName); //Allen Iverson
player3.fullName = 'Kobe Bryant'
console.log(player3.fullName); //Kobe Bryant
静态属性
class Player {
firstName:string = 'Allen'
static lastName:string = 'Iverson'
}
console.log(Player.lastName);
console.log(new Player().firstName);
静态属性以static关键字进行修饰,不需要实例化就能直接通过类名进行访问
而一般属性则需要通过实例化对象才能进行访问,类似于构造函数。
function Player () {
const lastName = 'Iverson'
}
Player.prototype.firstName = 'Allen'
抽象类
抽象类做为其它派生类的基类使用。 它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。
abstract class Player{
abstract skills()
}
class Cba extends Player{
skills(){
console.log('我会投篮');
}
}
class Nba extends Player{
skills(){
console.log('Best Crossover');
}
}
const GuoAilun = new Cba()
GuoAilun.skills() //我会投篮
const iverson = new Nba()
iverson.skills() //Best Crossover
//无法创建抽象类的实例。不被允许的
const p1 = new Player()
四、函数
函数是 JavaScript 应用程序的基础,它帮助你实现抽象层,模拟类,信息隐藏和模块。在 TypeScript 里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。TypeScript 为 JavaScript 函数添加了额外的功能,让我们可以更容易地使用。
基本示例
//命名函数
function sum1 (x,y){
return x + y
}
//匿名函数
let sum2 = function (x,y) {
return x + y
}
函数类型
//命名函数
function sum1 (x:number,y:number):number{
return x + y
}
//匿名函数
let sum2 = function (x:number,y:number):number {
return x + y
}
完整函数类型
let sum2:(x:number,y:number) => number = function (x:number,y:number):number{
return x + y
}
可选参数
TypeScript 里的每个函数参数都是必须的。 这不是指不能传递 null 或 undefined 作为参数,而是说编译器检查用户是否为每个参数都传入了值。编译器还会假设只有这些参数会被传递进函数。 简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。
JavaScript 里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是 undefined。 在TypeScript 里我们可以在参数名旁使用 ? 实现可选参数的功能。
在 TypeScript 里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是 undefined 时。 它们叫做有默认初始化值的参数。
function getMsg (msg1:string = '这是一段信息',msg2?:string):string{
if(msg2) return msg1 + msg2
return msg1
}
console.log(getMsg()); //这是一段信息
console.log(getMsg('你好啊'));
console.log(getMsg('你好啊,','李银河'));
剩余参数
必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在 JavaScript 里,你可以使用 arguments 来访问所有传入的参数。
在 TypeScript 里,你可以把所有参数收集到一个变量里:
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( …)后面给定的名字,你可以在函数体内使用这个数组。
function contact (x:string,...args:Array<string>){
console.log(x,...args);
}
contact('a','b','c','d','e') //a, b, c, d, e
函数重载
函数重载:函数名相同,而形参不同的多个参数,如通过add方法实现 数字求和 和 字符串拼接 两个功能
这个功能主要为了实现 传入非法数据时,ts报红,提示错误的信息内容
在javascript中,因为是弱类型,所以形参可以和实参不匹配,没有函数重载这种说法,但是typescript中,同其他面向对象语言一样(如java)就存在此语法
js中代码如下
function add(x, y) {
if (typeof x === typeof y) return x + y
return '参数非法'
}
console.log(add('1', '2')); //12
console.log(add(1, 2)) //3
console.log(add('1', 2)) //参数非法
ts代码如下
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,2));
console.log(add('1','2'));
//error 没有与此调用匹配的重载。
//第 1 个重载(共 2 个),“(x: string, y: string): string”,出现以下错误。
//类型“number”的参数不能赋给类型“string”的参数。
//第 2 个重载(共 2 个),“(x: number, y: number): number”,出现以下错误。
//类型“string”的参数不能赋给类型“number”的参数。
console.log(add(1,'2'));
五、泛型
指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。
函数泛型
function createArr <T> (x:T, count:number):Array<T>{
let arr:Array<T> = []
for(let i = 0; i < count; i++){
arr.push(x)
}
return arr
}
console.log(createArr('李莫愁',3)); //["李莫愁", "李莫愁", "李莫愁"]
console.log(createArr(1024,3)); //[1024, 1024, 1024]
console.log(createArr({name:'李莫愁',age:1024},3)); //同上面效果一样push了三个对象
泛型接口
在定义接口时, 为接口中的属性或方法定义泛型类型
在使用接口时, 再指定具体的泛型类型
interface IPersonManager <T,K>{
perData:T[]
addPerson:(t:T) => void
getPersonByName:(name:string) => T | K
}
class Person {
name:string
age:number
gender:string
constructor(name:string,age:number,gender:string){
this.name = name
this.age = age
this.gender = gender
}
}
class PersonManager implements IPersonManager<Person,string> {
perData:Person[] = []
addPerson(person:Person,...args:Person[]):void{
this.perData.push(person,...args)
}
getPersonByName(name:string):Person|string{
let someone = null
this.perData.filter(item => {
if(item.name ===name){
someone = item
}
})
return someone || '查无此人'
}
}
const p1 = new Person('李莫愁',23,'女')
const p2 = new Person('陆无双',18,'女')
const p3 = new Person('程英',20,'女')
const p4 = new Person('郭芙',19,'女')
const p5 = new Person('郭襄',6,'女')
const p6 = new Person('完颜萍',15,'女')
const personStore = new PersonManager()
personStore.addPerson(p1,p2,p3,p4,p5,p6)
console.log(personStore.perData);
//(6) [Person, Person, Person, Person, Person, Person]
console.log(personStore.getPersonByName('程英'));
//Person {name: "程英", age: 20, gender: "女"}
console.log(personStore.getPersonByName('小龙女')); //查无此人
泛型类
在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型
class DoSomething <T> {
defaultValue:T
add:(x:T,y:T) => T
}
let numSum = new DoSomething<number>()
numSum.defaultValue = 100
numSum.add = function(x:number,y:number):number{
return this.defaultValue + x + y
}
console.log(numSum.add(1,2)); //103
let strCon = new DoSomething<string>()
strCon.defaultValue = '千年等一回,'
strCon.add = function(x:string,y:string):string{
return this.defaultValue + x + y
}
console.log(strCon.add('啊啊','啊啊啊')); //千年等一回,啊啊啊啊啊
泛型约束
如果我们直接对一个泛型参数取 length
属性, 会报错, 因为这个泛型根本就不知道它有这个属性
function foo <T> (x:T):number{
return x.length //error
}
我们可以用泛型约束来实现
interface hasLength {
length: number
}
//指定泛型约束
function foo <T extends hasLength> (x:T):number{
return x.length //error
}
我们需要传入符合约束类型的值,必须包含必须 length 属性:
interface hasLength {
length: number
}
//指定泛型约束
function foo <T extends hasLength> (x:T):number{
return x.length //error
}
console.log(foo('hellow world')); //12
console.log(foo(123)); //error 类型“number”的参数不能赋给类型“hasLength”的参数。
const bar = {
length:520
}
console.log(foo(bar)); //520