函数
函数中的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并没有name
、score
这些属性。
知道原因我们就可以很好的解决这个问题,利用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)
Vue3+Typescript的项目体验
lhz960904/movie-trailer: Vue3 + TypeScript开发的电影预告片webAPP,可以查看正在热映与即将上映的电影信息和短片 (github.com)
任务
- 基本类型
- 函数参数、返回值
- interface接口
- 数组定义
- 泛型
- 基本使用
- 多泛型
- 泛型接口
- 泛型约束
- 泛型约束与索引类型
- 类型断言