TypeScript 函数介绍

59 阅读8分钟

函数

函数中的this

在JavaScript中,this使用可能有时候并不是如自己所想的那样

但是好消息是,TypeScript中会提示你是否正确的使用了this。 如下面的例子中,

class Person{
    name:string = ''   //如果不定义这个name属性,后续的this操作都将出现问题
    setName(name:string):void{
        this.name = name  //必须先定义上面的name,此处的this才允许设置name属性
    }
    showName():void{
        console.log(this.name); //此处在调用this的时候也能自动提示,this下的属性\方法
    }
}

let p = new Person()
p.setName('张三')
p.showName()

箭头函数与this

首先来个例子,Javascript中写这段的代码,只有在运行阶段才会出现问题

let stu = {
    name:'张三丰',
    score:100,
    showInfo(){
        return function(){
            console.log(`姓名:${this.name},分数:${this.score}`);
        }
    }
}

let showInfo = stu.showInfo()
showInfo() 
// 期望打印: 姓名:张三丰,分数:100
// 实际情况: 姓名:undefined,分数undefined

可以看到,这里预期的结果和实际结果并不一致。原因其实也很简单,因为showInfo运行在全局作用域下,而调用时,showInfo中的this为window,而window并没有namescore这些属性。 知道原因我们就可以很好的解决这个问题,利用ES6的箭头函数:

let stu = {
    name:'张三丰',
    score:100,
    showInfo(){
        return ()=>{  //此处如果用普通函数,内部使用this会提示错误
            console.log(`姓名:${this.name},分数:${this.score}`); 
        }
    }
}

箭头函数有几个使用注意点。 (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。 (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。 (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

更多关于ES6箭头函数请看传送门:箭头函数

函数重载 (Overload)

什么是函数重载?

重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

在TypeScript中,允许我们为函数定义不同参数返回不同类型,例如下面的例子: 实现一个翻转函数,参数类型可以为数字、字符串、数组,需要翻转后返回对应类型的结果

function reverse(arg:string|number|Array<any>):string|number|Array<any>{
    if(typeof arg === 'string'){
        return arg.split('').reverse().join('')
    }else if(typeof arg === 'number'){
        return arg.toString().split('').reverse().join('')
    }else{
        return arg.reverse()
    }
}

上面这段函数虽然也能实现功能,但是不能够精确表达函数的作用细节。 可以通过重载的方式,使其更加准确

function reverse(arg:string):string  //当传入字符串的时候,返回字符串
function reverse(arg:number):number  //当传入数字的时候,返回数字
function reverse(arg:Array<any>):Array<any>
function reverse(arg:string|number|Array<any>):string|number|Array<any>{
    if(typeof arg === 'string'){
        return arg.split('').reverse().join('')
    }else if(typeof arg === 'number'){
        return arg.toString().split('').reverse().join('')
    }else{
        return arg.reverse()
    }
}

console.log(reverse('hello'));
console.log(reverse(true)); //这样传递,会提示函数重载模式问题

泛型

介绍

泛型是 TypeScript 中非常重要的一个概念,因为在之后实际开发中任何时候都离不开泛型的帮助,原因就在于泛型给予开发者创造灵活、可重用代码的能力。

可以简单理解为:广泛的类型

初识泛型

假设我们用一个函数,它可接受一个 number 参数并返回一个 number 参数。

function returnItem (para: number): number {
    return para
}

我们按以上的写法貌似是没问题的,那么如果我们要接受一个 string 并返回同样一个 string 呢?逻辑是一样的,但是仅仅是类型发生了变化,难道需要再写一遍?

function returnItem (para: string): string {
    return para
}

这明显是重复性的代码,我们应该如何才能避免上述情况呢? 难道我们只能用 any 表示了?

function returnItem (para: any): any {
    return para
}

我们现在的情况是,我们在静态编写的时候并不确定传入的参数到底是什么类型,只有当在运行时传入参数后我们才能确定。 那么我们需要变量,这个变量代表了传入的类型,然后再返回这个变量,它是一种特殊的变量,只用于表示类型而不是值。 这个类型变量在 TypeScript 中就叫做「泛型」。

function returnItem<T>(para: T): T {
    return para
}

我们在函数名称后面声明泛型变量 ,它用于捕获开发者传入的参数类型(比如说string),然后我们就可以使用T(也就是string)做参数类型和返回值类型了。 ​

多个类型参数

定义泛型的时候,可以一次定义多个类型参数,比如我们可以同时定义泛型 T 和 泛型 U:

// 输入一个数组,输出该数组的逆序,并确保类型一致
function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

泛型接口

泛型也可用于接口声明,以上面的函数为例,如果我们将其转化为接口的形式。

interface ReturnItemFn<T> {
    (para: T): T
}

那么当我们想传入一个number作为参数的时候,就可以这样声明函数:

const returnItem: ReturnItemFn<number> = para => para

泛型类

泛型除了可以在函数中使用,还可以在类中使用,它既可以作用于类本身,也可以作用与类的成员函数。 我们假设要写一个栈数据结构,它的简化版是这样的:

class Stack {
    private arr: number[] = []

    public push(item: number) {
        this.arr.push(item)
    }

    public pop() {
        this.arr.pop()
    }
}

同样的问题,如果只是传入 number 类型就算了,可是需要不同的类型的时候,还得靠泛型的帮助。

class Stack<T> {
    private arr: T[] = []

    public push(item: T) {
        this.arr.push(item)
    }

    public pop() {
        this.arr.pop()
    }
}

泛型类看上去与泛型接口差不多, 泛型类使用 <> 括起泛型类型,跟在类名后面。

泛型约束

现在有一个问题,我们的泛型现在似乎可以是任何类型,但是我们明明知道我们的传入的泛型属于哪一类,比如属于 number 或者 string 其中之一,那么应该如何约束泛型呢?

class Stack<T> {
    private arr: T[] = []

    public push(item: T) {
        this.arr.push(item)
    }

    public pop() {
        this.arr.pop()
    }
}

我们可以用 的方式约束泛型,比如下图显示我们约束泛型为 number 或者 string 之一,当传入 boolean 类型的时候,就会报错。

type Params = number | string
class Stack<T extends Params> {
    private arr: T[] = []

    public push(item: T) {
        this.arr.push(item)
    }

    public pop() {
        this.arr.pop()
    }
}

let a = new Stack<number>()
a.push(1)
a.push(2)
a.push('hello') //类型“string”的参数不能赋给类型“number”的参数
a.push(true)  //类型“boolean”的参数不能赋给类型“Params”的参数

泛型约束与索引类型

我们先看一个常见的需求,我们要设计一个函数,这个函数接受两个参数,一个参数为对象,另一个参数为对象上的属性,我们通过这两个参数返回这个属性的值,比如:

function getValue(obj: object, key: string) {
  return obj[key] // error
}

我们会得到一段报错,这是新手 TypeScript 开发者常常犯的错误,编译器告诉我们,参数 obj 实际上是 {},因此后面的 key 是无法在上面取到任何值的。 因为我们给参数 obj 定义的类型就是 object,在默认情况下它只能是 {},但是我们接受的对象是各种各样的,我们需要一个泛型来表示传入的对象类型,比如 T extends object:

function getValue<T extends object>(obj: T, key: string) {
  return obj[key] // error
}

这依然解决不了问题,因为我们第二个参数 key 是不是存在于 obj 上是无法确定的,因此我们需要对这个 key 也进行约束,我们把它约束为只存在于 obj 属性的类型,这个时候需要借助到后面我们会进行学习的索引类型进行实现 ,我们用索引类型 keyof T 把传入的对象的属性类型取出生成一个联合类型,这里的泛型 U 被约束在这个联合类型中,这样一来函数就被完整定义了:

function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
  return obj[key] // ok
}

比如我们传入以下对象:

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

getValue(person,'age') //类型“"age"”的参数不能赋给类型“"name" | "score"”的参数。

这个时候 getValue 第二个参数 key 的类型被约束为一个联合类型 name | id,他只可能是这两个之一,因此你甚至能获得良好的类型提示。

类型断言

有些情况下 TS 并不能正确或者准确得推断类型,这个时候可能产生不必要的警告或者报错。 比如初学者经常会遇到的一类问题:

const person = {};

person.name = '张三丰'; // Error: 'name' 属性不存在于 ‘{}’
person.age = 20; // Error: 'age' 属性不存在于 ‘{}’

这个时候该怎么办?由于类型推断,这个时候 person 的类型就是 {},根本不存在后添加的那些属性,虽然这个写法在js中完全没问题,但是开发者知道这个 person 实际是有属性的,只是一开始没有声明而已,但是 typescript 不知道啊,所以就需要类型断言了:

interface Person {
  name: string;
  age: number;
}

const person = {} as Person;

person.name = '张三丰';
person.age = 20;

但是类型断言不要滥用,在万不得已的情况下使用要谨慎,因为你强制把某类型断言会造成 TypeScript 丧失代码提示的能力。 ​

Typscript的拓展参考文献

[另外一个参考文档](TypeScript 入门教程 (xcatliu.com))

TypeScript中文网 · TypeScript——JavaScript的超集 (tslang.cn)

Vue技术栈开发管理平台的方案 (JS生态)

vue-element-admin (panjiachen.github.io)

Workplace - Ant Design Pro (antdv.com)

内置全局样式 - View UI 专业版 (iviewui.com)

NaiveUI生态 (TS生态)

Home | Naive-Ui-Admin (jekip.github.io)

Naive UI: 一个 Vue 3 组件库

Home | Vben Admin (vvbin.cn)

Vue3+Typescript的项目体验

lhz960904/movie-trailer: Vue3 + TypeScript开发的电影预告片webAPP,可以查看正在热映与即将上映的电影信息和短片 (github.com)

任务

  1. 基本类型
  2. 函数参数、返回值
  3. interface接口
  4. 数组定义
  5. 泛型
    • 基本使用
    • 多泛型
    • 泛型接口
    • 泛型约束
    • 泛型约束与索引类型
  6. 类型断言