TypeScript语法

188 阅读5分钟

一、基础数据类型

布尔类型(boolean)

布尔类型很简单,只需要在其定义后面添加类型然后赋值即可:

let isClose:boolean=true

ew 或者调用Boolean方法:

let isClose:boolean= Boolean(true)

其允许值只有两种:true/false,如果不是这两种,编译器就会报错:

let isClose:boolean=2

报错如下: Type '2' is not assignable to type 'boolean'.

需要注意的是,使用Boolean构造函数返回的是Boolean对象,所以不能赋值给boolean类型的数据

let isClose:boolean=new Boolean(true)//Type 'Boolean' is not assignable to type 'boolean'.

数值类型(number)

数值类型用number来定义:

let num: number = 6
let num2: number = Number(2)//调用number
let num3: number = new Number(2)//调用构造函数,报错:Type 'Number' is not assignable to type 'number'.
let hexNum: number = 0xf00d//十六进制
let binaryNum: number = 0b1010 //二进制表示法
let octalNum: number = 0o744//八进制表示法
let notANumber: number = NaN
let infinityNumber: number = Infinity

字符串类型(string)

使用string定义字符:

let firendNum:number=20
let myName:string='jepson'
let lastName:string=String('smith')
let addres:string=new String('smith')//报错:Type 'String' is not assignable to type 'string'.
//等价于lastName+myName+' has '+firendNum+' friends'
let message=`${myName}·${lastName} has ${firendNum} friends`

null、undefined

在TypeScript中null、undefined是所有类型的子类型,所有其他类型都能够被赋值为null和undefined:

let u:undefined=undefined
let n:null=null
let num:number=undefined
let str:string=null

你可能会问,为什么在Vue项目中不能这么用? 因为Vue项目中默认配置了--strictNullChecks标记,这样也能避免许多错误,所以如果想要能够赋值null或者undefined,最好显式声明:

let name:string|null|undefined=null

symbol(永远唯一的值)

Symbol是ES6的新原生类型,表示永远不可变且唯一的值,如果你还不了解这个类型,可以去看阮一峰编写的《ECMAScript6入门》Symbol篇。在TypeScript中表现为symbol

let globalKey:symbol=Symbol()
let globalKey2:symbol=Symbol()
console.log(globalKey===globalKey2)//false

空值(void)

void表示空值,只能赋值为null/undefined(所有类型的子类型),如果用于变量中这或许没什么用,但是用于函数中表示不返回任何值:

let myFirstName:void=undefined
let myLastName:void=null
function fn1():void {
  return null
}
function fn2():void {
  return undefined
}
function fn3():void {
  console.log('this is fn3')
}

any(天使还是魔鬼?)

Typescript容易被写成anyScript any用于不确定其类型时所指定的类型,表示任何类型,如:第三方库、后端返回数据不确定(尽量要求后端返回的数据确定)、动态内容等:

let anyValue:any=true
anyValue=20
anyValue='anyValue'
anyValue=Symbol()
anyValue=null
anyValue=undefined
anyValue=new Error('anyvalue')
anyValue=['anyValue']

有人戏称:

any是TypeScript最大的Bug

注意:指定any类型之后编辑器和编译器将不会进行检查,这或许有许多便捷之处,但是从另一个角度来说,不进行检查也就代表许多有关的错误在编辑器中或者编译阶段不会报错,所以应尽量避免在项目中使用any

二、函数

函数在Javascript中非常重要,因为所有行为都是通过函数来执行。在《函数式编程指南》一书中指出:

函数是Javascript的一等公民

TypeScript中的函数,需要把输入和输出都考虑进去:

//函数声明式
function sum(a:number,b:number):number{
    return a+b
}

//函数表达式
const reduce= (a:number,b:number):number=>a-b

console.log(sum(1,1))//2
console.log(reduce(1,1))//0

函数中的类型

如上面代码,函数需要指定输入和输出的类型:

  • 少输入参数、多输入参数、参数类型不符合都会报错

  • 如果不指定输入的类型,默认为any(不推荐使用any,所以Vue项目中会报错)

  • 不指定输出类型则默认为void,表示无返回结果;如果有返回,TypeScript会根据返回数据自动推断类型

    function runAway() {
    console.log('这是js函数')
    }
    

    上面代码等价于下面的代码:

    function runAway():void {
    console.log('这是ts函数')
    }
    

下面是个自动推断类型的例子:

function add(num1:number,num2:number) {
  return num1+num2
}

//自动推断其类型
const num:number=add(1,2)

可选参数与默认参数

可选参数

对于一些参数不是必须的情况下,可以使用可选参数,可选参数使用?:定义,表示这个参数是非必须的:

function haveLaunch(food:string,cuisine:string,fruits?:string){
    console.log(`今天中午吃:${food}${cuisine}${fruits}`);
}
haveLaunch('米饭','豆芽',undefined)

如果填入可选参数,必须按照顺序传入


需要注意的是:可选参数后面不能带必选参数

默认参数

默认参数是ES6的一个新特性,如果还不太了解这个特性的同学,可以参考阮一峰写的《ECMAScript入门教程》函数篇 用于为那些非必填项设置默认值:

function haveLaunch(food:string,cuisine:string,fruits:string='没有水果'){
    console.log(`今天中午吃:${food}${cuisine}${fruits}`);
}
haveLaunch('米饭','豆芽')

默认参数与可选参数不同:

  1. 默认参数没有位置限制,第一个参数也能设置默认参数,后面可以带必选参数
  2. 默认参数如果传入undefinednull不会取代默认值,而可选参数会直接复制;利用这一特性可以为每个参数设置默认值,如果是默认值可以传入undefinednull,

=>与箭头函数

你一定好奇,=>不是ES6箭头函数吗,这还有什么可讲的?

下载 (1).jpg 在TypeScript类型定义中=>用于表示函数,左边是输入类型,右边是输出类型; 而在ES6中,=>代表的是一个箭头函数,左边参数,右边代码块

下面例子中展示了TypeScript类型定义中的和ES6的=>之间的区别

let add: (num1: number, num2: number) => number   //类型表达
add = (num1, num2) => num1 + num2   //根据上面的类型表达,定义了一个函数

rest参数(剩余参数)

rest参数是ES6的新特性,还不了解的同学请看传送门:《ECMAScript入门教程》函数篇 在TypeScript中我们可以为rest参数设定类型:

function sum(a:number,b:number,...numArr:number[]):number{
    let num = a + b
    numArr.forEach((n:number)=>{
        num+=n
    })
    return num
}
console.log(sum(1,2,3,4,5)); //15

三、interface(接口)

在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。

TypeScript中的interface(接口)非常灵活,常用与对类的部分行为抽象、规定对象的形状(哪些属性/方法)

interface User {
  username:string;   //注意!!! 此处为分号
  password:string;
}

//下面这个写法会报错
const u:User={
  username: 'admin',
  password: 12345678,//报错:Type 'number' is not assignable to type 'string'.
  level:1//报错:Type '{ username: string; password: string; level: number; }' is not assignable to type 'User'.
}

//这个写法不会报错
const u2:User={
  username: 'admin',
  password: '12345678'
}

可选属性

interface规定了对象应该有那些属性,而有时候对象的某些属性并不是固定有的,这时候我们可以定义一个可选属性,即使缺少可选属性,仍然不会报错:

interface User {
  username:string;
  password:string;
  level?:number
}

const u:User={
  username: 'admin',
  password: '12345678'
}

只读属性

有这样一种应用场景:希望对象在创建的时候初始化值,而一旦初始化之后便不能改变其值。这时候只读属性就派上用场了:

interface User {
  readonly username:string;
  password:string;
  level?:number
}

const u:User={
  username: 'admin',
  password: '12345678',
  level:1
}

u.username='Admin'//报错:Cannot assign to 'username' because it is a read-only property.

函数属性

那么,如何指定对象中的函数呢? 采用functionName(...args:dataType):returnType的形式指定:

interface User{
  username:string;
  password:string;
  level:number;   //如果此处的level设为可选参数,则ts认为其可能为undefined,将抛出错误
  update(lv:number):number
}

let u:User = {
  username:'张三丰',
  password:'123',
  level:2,
  update(lv:number){
    this.level += lv
    return this.level
  }
}

console.log(u.level);
u.update(2)
console.log(u.level);

动态可索引属性实现伪数组

前面说过,TypeScript的interface非常灵活,可以用动态可索引属性,所以我们可以利用这点实现伪数组类型:

interface PseudoArray{
  [index:number]:number
}

const pseudoArray={
  0:20,
  1:11,
  2:33
}

或者动态对象:

interface Person{
  [key:string]:string
}

let p:Person = {
  name:'张三丰',
  score:'100'
}

let p1:Person = {
  name:'张三',
  age:10    //此处不符合string类型
}

接口定义函数类型

可以使用接口为函数定义类型:

interface Sum{
  (a:number,b:number):number
}

let sum:Sum = (a,b)=>{
  return a+b
}

console.log(sum(2,'3')); //报错,此处的'3'不符合类型要求
console.log(sum(2,3));

接口间的继承

什么是继承?以下原话摘自维基百科:

继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类的行为在编译期就已经决定,无法在运行期扩展。 有些编程语言支持多重继承,即一个子类可以同时有多个父类,比如C++编程语言;而在有些编程语言中,一个子类只能继承自一个父类,比如Java编程语言,这时可以透过实现接口来实现与多重继承相似的效果。 现今面向对象程序设计技巧中,继承并非以继承类别的“行为”为主,而是继承类别的“类型”,使得组件的类型一致。另外在设计模式中提到一个守则,“多用合成,少用继承”,此守则也是用来处理继承无法在运行期动态扩展行为的遗憾。

TypeScript中的继承比较灵活,能够多重继承,用extends实现:

interface Animal {  //基本动物行为
  eat(food:string):void;
  run():void,
  sleep():void
}

interface Person extends Animal { //新增人类属性
  name:string,
  gender:'GIRL'|'BOY'
}

interface Boy extends Person { 
  age:number  //男生对于年龄不太敏感
}

interface Girl extends Person {
  age:18 //女生年龄永远18岁
}

let lilei:Boy = {  //此处定义lilei的时候,TS会约束并提示其内部属性
  name:'李雷',
  gender:'BOY',
  age:20,
  eat(food){
    console.log(`${this.name}开始吃${food}了`);
  },
  run(){
    console.log(`${this.name}开始跑步了`);
  },
  sleep(){
    console.log(`${this.name}开始睡觉了`);
  }
}

lilei.eat('水果')
lilei.run()
lilei.sleep()

let hanmeimei:Girl = {
  name:'韩梅梅',
  gender:'GIRL',
  age:18,   //此处如果尝试设置其他年龄,则会报错
  eat(food){
    console.log(`${this.name}开始吃${food}了`);
  },
  run(){
    console.log(`${this.name}开始跑步了`);
  },
  sleep(){
    console.log(`${this.name}开始睡觉了`);
  }
}

hanmeimei.eat('蔬菜')
hanmeimei.run()
hanmeimei.sleep()

四、数组

TypeScript定义数组类型,使得数组中不能够有除了指定类型之外的其他类型,在编辑器和编译阶段会报错(但是不影响编译)。 接下来我们看看怎么定义数组

类型+数组字面量定义

let numArr:number[]
numArr=[1,2,3]
numArr=[1,2,3,'4']//报错:Type 'string' is not assignable to type 'number'.
numArr=[1,2,3,false]//报错:Type 'false' is not assignable to type 'number'.

数组泛型定义数组

数组泛型支持定义多种类型

let numArr:Array<string|number>
numArr=[1,2,3]
numArr=[1,2,3,'4']
numArr=[1,2,3,false]//报错:Type 'false' is not assignable to type 'number'.

在实际开发中,更推荐这种方式定义数组,因为看上去更加明了、灵活,更像是一个类型

接口定义数组

前面说过,interface是个很重要的概念,它很灵活、能够应用于多种场景,除了可以用来定义伪数组,也可以用来定义真数组,其类型定义和数组泛型一样灵活:

interface NumberArray{
  [index:number]:number|string
}
let numArr:NumberArray
numArr=[1,2,3]
numArr=[1,2,3,'4']//报错:Type 'string' is not assignable to type 'number'.
numArr=[1,2,3,false]//报错:Type 'false' is not assignable to type 'number'.

元组

对于指定元素数量和类型的数组,推荐使用元组定义:

let position:[number,number]  //定义一个经纬度位置信息
position = [106.23,66.8]

枚举

以下摘自维基百科:

数学计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。 枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。

枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。 ​

当满足以下条件时,枚举成员被当作是常数:

  • 不具有初始化函数并且之前的枚举成员是常数。在这种情况下,当前枚举成员的值为上一个枚举成员的值加 1。但第一个枚举元素是个例外。如果它没有初始化方法,那么它的初始值为 0。
  • 枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常数枚举表达式:
    • 数字字面量
    • 引用之前定义的常数枚举成员(可以是在不同的枚举类型中定义的)如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用
    • 带括号的常数枚举表达式
    • +, -, ~ 一元运算符应用于常数枚举表达式
    • +, -, *, /, %, <<, >>, >>>, &, |, ^ 二元运算符,常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为 NaN 或 Infinity,则会在编译阶段报错

TypeScript的枚举(enum)非常灵活,默认自动赋值,比如定义一周的天数:

enum Week{
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
}

console.log(Week[1]);

手动赋值

enum Week{
  Monday=1,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
}

console.log(Week[1]);

枚举的应用场景

// 枚举使用场景:后端下发的订单状态是以编码的方式来代表不同的状态
// 例如:10:待支付,15:待支付尾款 20:待发货,30:待收货,40:已完成,50:已关闭,60退款中,70已退款
// 我们通过异步请求拿到了后端的数据包如下
let glist = [
  {name:'商品1',price:9.9,status:10},
  {name:'商品2',price:19.9,status:40},
  {name:'商品3',price:99,status:15},
]

// 思考:在没有枚举的情况下如何将商品的订单状态汉字准确显示在前端页面?

// 使用枚举处理:
enum OrderStatus{
  '待支付'=10,
  '待支付尾款'=15,
  '待发货'=20,
  '待收货'=30,
  '已完成'=40
}

// 如果要展示第一条商品的订单状态
console.log(OrderStatus[glist[0].status]);