前言
JavaScript是一门弱类型语言,无需编译就能运行,没有类型检查,无法提前预知错误,并且没有类型约束增加了沟通成本。我们知道,在开发时,越早发现错误越好。TypeScript是拥有类型的JavaScript超集,相当于JavaScript的加强版,它可以编译成完整的JavaScript代码。
JavaScript所拥有的特性,TypeScript全部都是支持的,并且它紧随ECMASript的标准,所以ES6、ES7、ES8等新语法标准,它都是支持的;TypeScript在语言层面上,不仅仅增加了类型约束,而且包括一些语法的扩展,比如枚举类型,元组类型等。
变量的声明
// var/let/const 标识符: 数据类型 = 值;
let message: string = '张三';
//当我们给 message 赋值其他类型的值,那么就会报错
message: 123 //不能将类型“number”分配给类型“string”。ts(2322)
变量的类型推导
// 有时候我们开发时不需要每次声明变量都要事先定义好数据类型,而是可以通过ts的类型推导来推断出变量类型
let age = 123
age = '123' // 不能将类型“string”分配给类型“number”。ts(2322)
数据类型
/* JavaScript */
let age: number = 22 //number
let name: string = 'zhangsan' //string
let isReload: boolean = true //boolean
let arr: number[] = [1, 2, 3] //Array
let arr1: Array<number> = [4, 5, 6]
let obj: object = { //object
name: 'zhangsan',
age: 12,
gender: 'male'
}
let s1: symbol = Symbol('zhangsan') //symbol
let s2: symbol = Symbol('zhangsan')
let person = {
[s1]: 'male',
[s2]: 'female'
}
let n: null = null // null
let u: undefined = undefined //undefined
/* TypeScript */
let a: any = 'why' // any 任意类型,宽松的(无约束)
a = 123 // 可以
let result: unknown; // unknown 类型不确定的变量
function sum(num1: number, num2: number): void { // void 通常用于指定函数没有返回值
console.log(num1 + num2)
}
function loopErr(): never { //never 表示永远不会发生值的类型,比如函数的死循环或者抛出异常(中断),无法 返回值
throw new Error()
}
let student: [string, number, string] = ['zhangsan', 12, 'male']; // tuple元组类型,即多种数据类型组合的数组
函数的参数类型
函数是JavaScript非常重要的组成部分,TypeScript允许我们指定函数的参数和返回值的类型。
function delete(id: string): object{ // 这里指定传入的参数必须为 string 返回值是一个对象
let res = {}
return res
}
function search(searchInfo: {name: string, age: number}): object{ // 这里指定传入的参数必须为对象且属性,属性值需要对应符合
let res = {}
return res
}
search({name: 'zhangsan', age: 2})
可选类型
-
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?:
-
注意可选类型后面不能有必选类型,即可选类型一般放最后,类似于…rest(剩余参数)
function search(id: string, name?: string) {
console.log(id)
if(name) console.log(name)
}
search(‘123’,‘zhangsan’) // 123,zhangsan
search(‘123’) // 123
联合类型
TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型
function printId(id: number | string) {
if(typeof id === 'number') return id
else return id.length
}
console.log(printId(10)) //10
console.log(printId('abc')) // 3
到这里其实可以看出,可选类型是某一类型与 undefined 的联合类型,如:
function printId(id?: number){
console.log(id)
}
printId()
printId(123) // 123
类型别名
在前面,我们通过在类型注解中编写 对象类型 和 联合类型,但是当我们想要多次在其他地方使用时,就要编写多次。 比如我们可以给对象类型起一个别名:
type Point = {x: number, y: number}
function printPoint(point:Point) {
console.log(Point.x, Point.y)
}
printPoint({x: 1, y: 2})
类型断言as
有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言
// 不使用断言
function printLength(n: string | number) {
console.log(n.length); //类型“string | number”上不存在属性“length”。类型“number”上不存在属性“length”。ts(2339)
}
// 使用断言
function printLength(n: string | number) {
console.log((n as string).length);
}
函数的默认参数
从ES6开始,,JavaScript是支持默认参数的,TypeScript也是支持默认参数的:
function getInfo(id: string, status: number = 2) {
console.log(id, status)
}
foo('213') // 213, 2
// 这个时候y的类型其实是 undefined 和 number 类型的联合。
函数的剩余参数
从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。
function sum(...nums: number[]) {
let total = 0
for(let num of nums) {
total += sum
}
console.log(total)
}
sum(1, 2, 3) // 6
函数的重载
function sum(num1: number, num2: number): number;
function sum(num1: string, num2: string): string; //重载外部不可见
function sum(num1: any, num2: any): any {
return num1 + num2
}
console.log(sum(1, 2)) // 3
console.log(sum('zhang', 'san')) // zhangsan
接口的声明
interface People = {
name: string
age: number
}
-
接口中的可选属性
interface Person {
name: string
age: number
gender?: string
}
const person: Person = {
name: ‘zhangsan’,
age: 15,
gender: ‘female’
}
console.log(person.name) // zhangsan
console.log(person?.gender) // female -
接口中的只读属性
interface Person {
readonly name: string
readonly age: number
gender?: string
}
const person: Person = {
name: ‘zhangsan’,
age: 12,
gender: ‘male’
}
person.age = 14 //无法分配到 “age” ,因为它是只读属性。ts(2540)
可以发现,readonly 和 const 有像似之处
- 同:用于约束数据的修改属性
- 异:const用于定义常量,而 readonly用于修饰属性
索引类型
interface Game {
[index: string]: string
}
const game: Game = {
'first': '天天酷跑',
'second': '五子棋',
'third': '象棋'
}
console.log(game.first) //天天酷跑
接口继承
接口和类一样是可以进行继承的,也是使用extends关键字:
interface Person {
name: string,
eating: () => void
}
interface Animal {
running: () => void
}
interface Student extends Person, Animal {
gender: string
}
const student: Student = {
gender: 'male',
name: 'zhangsan',
eating: function() {},
running: function() {}
}
接口的实现
接口定义后,也是可以被类实现的:
-
如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入
-
这就是面向接口开发
interface ISwim {
swimming: () => void
}interface IRun {
running: () => void
}class Person implements ISwim, IRun {
swimming() {
console.log(‘swimming’)
}running() { console.log('running') }
}
function swim(swimmer: ISwim) {
swimmer.swimming()
}const p = new Person()
swim§ //swimming
交叉类型
交叉类型用 & 符号表示,表示需要满足多个类型的条件
type x = number & string // 没有同时满足条件的值,所以当交叉的类型是基本数据类型时相当于 never 类型
// 常用于对象类型进行交叉
interface Animal {
eating: () => void
}
interfact Dog {
running: () => void
}
type NewType = Dog & Animal
const dog: NewType = {
eating: function() {}
running: function() {}
}
枚举类型
枚举类型是TypeScript特性之一:
-
枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型
-
枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型
enum Direction {
top,
right,
bottom,
left
}
枚举类型的值
枚举类型默认是有值的,比如上面的枚举,默认值是这样的
enum Direction {
top = 0,
right = 1,
bottom = 2,
left = 3
}
// 当然也可以赋值给其他类型
enum Direction {
top = 'bottom',
right = 'left',
bottom = ,
left =
}
泛型
泛型是方便增强函数的复用性(抽象)
// 当我们需要封装一个函数, 传入一个参数,并且返回这个参数时。
function foo (arg: number): number { //实现了number类型,但不符合string、boolean等类型
return arg
}
// 使用 any
function foo1(arg: any): any {
return arg // 虽然可以实现需求,但这时候已经丢失了类型信息
}
// 使用泛型可以完美解决问题
function foo2<T>(arg: T): T {
return arg
}
console.log(foo2(1))
console.log(foo2(true))
console.log(foo2('sad'))
当需要传入多个类型时
function foo<T, E>(name: T, age: E) {
console.log(name, age)
}
泛型接口
在定义接口的时候我们也可以使用泛型
interface Animal<T> {
name: T,
friends: T[],
sayHello: (value: T) => void
}
const cat: Animal<string> = {
name: '蓝猫'
friends: ['虹猫','咖啡猫']
sayHello: function(value: string) {
console.log(value)
}
}
泛型类
class Animal<T> {
name: T
shape: T
constructor(name: T, shape: T) {
this.name = name
this. shape = shape
}
}
const dog = new Animal('二哈','middle')
const cat = new Animal<string>('蓝猫', 'small')
const pig: Animal<string> = new Animal('天蓬', 'big')
泛型约束
有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中
-
比如string和array都是有length的,或者某些对象也是会有length属性的
interface ILength {
length: number
}function getLength(args: T) {
return args.length
}
console.log(getLength(‘abc’)) //3
console.log(getLength([1,2,3])) //3
console.log(getLength({name: ‘zhangsan’, age: 12, length: 180})) //180
命名空间namespace
命名空间在TypeScript早期时,称之为内部模块,主要目的是将一个模块内部再进行作用域的划分,防止一些命名冲突的问题。
export namespace Time {
export function format(time: string) {
return '2022-01-27'
}
}
export namespace Prize {
export function format(price: number) {
return '九磅十五便士'
}
}