自学TS(三)

数组的类型

在 TypeScript中,数组类型有多种定义方式,比较灵活。

类型 + 方括号

最简单的方式是使用[类型 + 方括号]来表示数组:

let arr: number[] = [1, 2, 3, 4, 5];

上述例子中,规定了数组的类型为number,所以数组元素只能为number类型。

let arr: number[] = ["1", 2, 3, 4, 5];
// Type 'string' is not assignable to type 'number'.
// 类型'string'不能赋值给类型'number'。

数组一些方法的参数也会根据数组在定义时的类型进行限制:

let arr: number[] = [1, 2, 3, 4, 5];
arr.push("6");
// Argument of type 'string' is not assignable to parameter of type 'number'.
// 'string'类型的参数不能赋值给'number'类型的参数。

上述例子中,push方法只允许传入number类型的参数,但是最后缺传入了string类型的参数,所以报错了。

当我们数组需要多个类型的时候可以使用联合类型或者任意类型来进行处理。

let arr: (number | string)[] = ["1", 2, "3", 4, "5"];
// 联合类型

let arr: any[] = ["1", 2, "3", 4, "5", {number: 1}];
// 任意类型

数组泛型

我们也可以使用数组泛型(Array Generic)来表示数组:

let arr: Array<number> = [1, 2, 3, 4, 5]

用接口表示数组

接口也可以用来描述数组:

interface IAarray {
    [index: number]: number;
}
let arr: IArray = [1, 2, 3, 4, 5]

IAarray表示:数组的索引的类型是number,元素的类型也是number。

虽然接口也可以用来描述数组,但是我们一般不会这样使用,因为这种方式比之前的方式会麻烦很多,不过有一种情况例外,那就是用它来表示类数组。

类数组

类数组(Array-like Object)不是数组类型,比如arguments:

function sum() {
    let args: number[] = arguments;
}

// Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.
// 'IArguments'类型缺少'number[]'类型的以下属性:pop, push, concat, join等24个。

上例中,arguments实际上是一个类数组,不能用普通的数组的方式来描述,而应该用接口:

function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有length和callee两个属性。

事实上常用的类数组都有自己的接口定义,如IArguments,NodeList,HTMLCollection等:

function sum() {
    let args: IArguments = arguments;
}

其中IArguments是TypeScript中定义好了的类型,它实际上就是:

interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

多维数组

一个数组的元素可以是另外一个数组,这样就构成了多维数组(Multi-dimensional Array)。

let arr: number[][] = [[1, 2, 3], [1, 2, 3]];

数组在函数中的使用

作为参数传递给函数

let arr: number[] = [1, 2 ,3];
function forEachArr(arr: number[]) {
    arr.forEach(item => {
        console.log(item);
    });
}
forEachArr(arr);

作为函数的返回值

function arrFun(): number[] {
    return [1, 2, 3];
}
let arr: number[] = arrFun();
arr.forEach(item => {
    console.log(item);
});

函数的类型

函数声明

在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression):

// 函数声明(Function Declaration)
function sum(x, y) {
    return x + y;
}

// 函数表达式(Function Expression)
let mySum = function (x, y) {
    return x + y;
}

一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:

function sum(x: number, y: number): number {
    return x + y;
}

注意,输入多余的(或者少于要求的)参数,是不被允许的:

function sum(x: number, y: number): number {
    return x + y;
}
sum(1, 2, 3);
// Expected 2 arguments, but got 3.
// 预期有2个参数,但得到了3个。

function sum(x: number, y: number): number {
    return x + y;
}
sum(1);
// Expected 2 arguments, but got 1.
// 预期有2个参数,但得到了1个。

函数表达式

如果要我们现在写一个对函数表达式(Function Expression)的定义,可能会写成这样:

let mySum = function (x: number, y: number): number {
    return x + y;
};

这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的mySum,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给mySum添加类型,则应该是这样:

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

注意不要混淆了 TypeScript 中的=>和ES6中的=>。

在 TypeScript 的类型定义中,=>用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

用接口定义函数的形状

我们也可以使用接口的方式来定义一个函数需要符合的形状:

interface SearchFunc {
    (num1: number, num2: number): boolean;
}

let mySearch: SearchFunc;
mySearch = function(num1: number, num2: number) {
    return num1 - num2 > 0;
}

采用函数表达式|接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。

可选参数

前面提到,输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?

与接口中的可选属性类似,我们用?表示可选的参数:

function sum(x: number, y?: number): number {
    if(y) {
        return x + y;
    } else {
        return x;
    }
}
sum(1, 2);
sum(1);

需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了:

function sum(y?: number, x: number): number {
    if(y) {
        return x + y;
    } else {
        return x;
    }
}
sum(1, 2);
sum(undefined, 1);
// A required parameter cannot follow an optional parameter.
// 必选参数不能跟在可选参数后面。

参数默认值

在ES6中,我们允许给函数的参数添加默认值,TypeScript会将添加了默认值的参数识别为可选参数:

function sum(x: number, y: number = 2): number {
    if(y) {
        return x + y;
    } else {
        return x;
    }
}
sum(1, 2);
sum(1);

此时就不受「可选参数必须接在必需参数后面」的限制了:

function sum(y: number = 2, x: number): number {
    if(y) {
        return x + y;
    } else {
        return x;
    }
}
sum(1, 2);
sum(undefined, 1);

剩余参数

ES6中,可以使用...rest的方式获取函数中的剩余参数(rest 参数),事实上,items是一个数组。所以我们可以用数组的类型来定义它:

function push(array: any[], ...items: any[]) {
    array.push(...items);
}
let arr: any[] = [];
push(arr, 1, 2, 3, 4);

重载

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

比如,我们需要实现一个函数reverse,输入数字123的时候,输出反转的数字321,输入字符串'hello'的时候,输出反转的字符串'olleh'。

利用联合类型,我们可以这么实现:

function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。

这时,我们可以使用重载定义多个reverse的函数类型:

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

上例中,我们重复定义了多次函数reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

注意,TypeScript会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

箭头函数

ES6允许使用箭头=>定义函数,箭头函数提供了一种更加简洁的函数书写方式,箭头函数多用于匿名函数的定义;

let mySum = (x, y) => x + y;

上面的代码中,并没有对函数进行类型定义,所以还要添加上类型定义。

let mySum = (x: number, y: number): number => x + y;

上面的代码中,只是对等号右边的匿名函数进行了类型定义,所以还要对等号左边的mySum添加类型。

let mySum: (x: number, y: number) => number = (x: number, y: number): number => x + y;
posted @ 2023-04-11 10:08  要饭人士  阅读(69)  评论(0)    收藏  举报