首页 > 其他分享 >TS学习笔记四:函数及泛型枚举

TS学习笔记四:函数及泛型枚举

时间:2024-01-17 10:03:16浏览次数:38  
标签:function return 及泛 TS 枚举 参数 let card

  本节介绍ts的函数及泛型的相关内容,包括函数的声明格式及泛型的相关知识。

  1. B站视频 https://player.bilibili.com/player.html?aid=495956203

  2. 西瓜视频 https://www.ixigua.com/7321535978286514727

一、函数

  函数是js程序的基础,可以实现抽象层/模拟类/信息隐藏和模块,ts中已经支持类/命名空间和模块,但函数是主要定义行为的地方,ts为js函数添加了额外的功能。

1.函数

  可以创建带有名字的函数或匿名函数,实例如下:

function add(x, y) {
    return x + y;
}
let myAdd = function(x, y) { return x + y; };

函数可以使用函数体外部的变量,如:

let z = 100;
function addToZ(x, y) {
    return x + y + z;
}

2.函数类型

  可以给每个参数添加类型之后再为函数本身添加返回值类型,ts能够根据返回语句自动推断出返回值类型,示例如下:

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

  函数的完整类型定义如下:

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

  函数类型包含两部分:参数类型和返回值类型,使用参数列表的形式写出参数类型,为每个参数指定名字和类型,只要参数类型是匹配的就可以,参数名不一定一致。返回值部分的类型使用=>符号声明,返回值类型是函数类型的必要部分,若没有任何返回值,则可指定为void,但是不能留空。

3.推断类型

  赋值语句的时候一边指定了类型但是另一边没有类型的话,ts编译器会自动识别出类型,叫做按上下文归类,是类型推论的一种:

// myAdd has the full function type
let myAdd = function(x: number, y: number): number { return x + y; };

// The parameters `x` and `y` have the type number
let myAdd: (baseValue:number, increment:number) => number =function(x, y) { return x + y; };

4.可选参数和默认参数

  ts中每个函数参数都是必须的,编译器会检查用户是否为每个参数都传入了值,还会假设只有这些参数会被传递进函数,即传递给一个函数的参数个数必须与函数期望的参数个数一致。

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}
let result1 = buildName("Bob");                  // 报错,缺少参数
let result2 = buildName("Bob", "Adams", "Sr.");  //报错,多了参数
let result3 = buildName("Bob", "Adams");         // ah, just right

  js函数中的每个参数都是可选的,可传可不传,没传参数的时候,它的值就是undefined,在ts中可以在参数名旁边使用?实现可选参数的功能,如:

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
let result1 = buildName("Bob");  // works correctly now
let result2 = buildName("Bob", "Adams", "Sr.");  // 错误,多了参数
let result3 = buildName("Bob", "Adams");  // ah, just right

  可选参数必须跟在必须参数后面,否则会报错。也可以为参数提供一个默认值,即当用户没有传递参数或者传递的值是undefined时,就自动获取默认值作为参数的值,示例如下:

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}
let result1 = buildName("Bob");                  // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined);       // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr.");  // 异常,多个参数
let result4 = buildName("Bob", "Adams");         // ah, just right

  在所有必须参数后面的带默认初始化的参数都是可选的,与可选参数一样,在调用函数的时候可以省略,即可选参数与末尾的默认参数共享参数类型。

function buildName(firstName: string, lastName?: string) {
    // ...
}
function buildName(firstName: string, lastName = "Smith") {
    // ...
}

  带默认值的参数不需要放在必须参数的后面,如果带默认值的参数出现在必须参数的前面,用户必须明确的传入undefined值来获取默认值,如:

function buildName(firstName = "Will", lastName: string) {
    return firstName + " " + lastName;
}
let result1 = buildName("Bob");                  //异常,少参数
let result2 = buildName("Bob", "Adams", "Sr.");  // 异常,多了参数
let result3 = buildName("Bob", "Adams");         // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams");     // okay and returns "Will Adams"

5.剩余参数

  必要参数/默认参数和可选参数都表示某一个参数,若不知道有多少个参数传递的时候,可以使用arguments来访问所有传入的参数。ts中可以将所有参数收集到一个变量里:

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

  剩余参数会被当作个数不限的可选参数,可以一个都没有,也可以有任意多个,会创建参数数组,使用…指定,…后面给的的名字即时参数数组的名称,使用方式如下:

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

6.this和箭头函数

  js中this的值在函数被调用的时候才会指定,示例如下:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);

  上述示例代码会报错,因为createCardPicker函数返回的函数里的this被设置成了window而不是deck对象,因为独立调用了cardPicker,此时调用会将this视为window,严格模式下this为undefined而不是window。   若要正确的使用,可以在函数返回的时候就绑好正确的this,可以使用箭头函数,箭头函数会保存函数创建时的this值,而不是调用时的值,示例如下:

let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);

  上述代码中如果给编译器设置了--noImplicitThis标记。 它会指出this.suits[pickedSuit]里的this的类型为any。若需要明确具体的类型,可以提供一个显示的this参数,如下:

function f(this: void) {
    // make sure `this` is unusable in this standalone function
}
interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit);

  上述示例中Deck 接口的createCardPicker指定了this的类型,则调用时候this的类型就是Deck的,而不是any,此时--noImplicitThis就不会报错了。   有时候在回调函数中this也会报错,因为当函数被当作参数传递,并作为回调函数时,当回调被调用的时候,会被当作一个普通函数调用,this将为undefined,可以通过this参数来避免错误,如下:

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}

  this: void表示addClickListener期望onclick是一个不需要this类型的函数。

class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // oops, used this here. using this callback would crash at runtime
        this.info = e.message;
    };
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!

  指定了this类型后,因为显示的声明了onClickBad必须在Handler的实例上调用,ts会检测到addClickListener要求函数带有this: void,可以改变this类型修复此错误:

class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // can't use this here because it's of type void!
        console.log('clicked!');
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);

  因为onClickGood指定了this类型为void,因此传递addClickListener是合法的,但不能使用this.info,若都需要,则可以使用箭头函数了:

class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
}

  因为箭头函数不会捕获this,所以可以进行传递,但每个Handler对象都会创建一个箭头函数,方法指挥被创建一次,添加到Handler的原型链上,在不同的Handler对象间是共享的。

7.重载

  js中函数传入不同的参数可返回不同类型的数据,如下:

let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

  pickCard方法根据传入参数的不同会返回两种不同的类型,可以对函数提供多个函数类型定义来进行函数重载,编译器会根据这个列表去处理函数的调用,示例如下:

let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

  重载的pickCard函数在调用的时候会进行正确的类型检查,处理时会查找重载列表,尝试使用第一个重载定义,如果匹配则使用,因此使用的时候最好把精确的定义放在最前面,示例中function pickCard(x): any并不是重载列表的一部分,因此示例中只有两个重载,一个是接受对象另一个是接受数字,其他的参数将会产生错误。

二、泛型

  工程使用过程中,需要考虑可重用性,不仅能够支持当前的数据类型,也可以支持未来的数据类型。可以使用泛型来创建可重用的组件,让一个组件可支持多种类型的数据,示例如下:

function identity<T>(arg: T): T {
    return arg;
}
&emsp;&emsp;也可以使用any来定义函数,如:
function identity(arg: any): any {
    return arg;
}

  虽然使用any后函数能接受任何类型的参数,但是却丢失了一部分信息,即传入的类型和传出的类型应该是相同的,但是实际不确定具体的类型,使用了泛型后就能确保传入的参数和传出的值类型是一致的。泛型不会丢失信息,定义了泛型函数之后,有两种调用方式: 第一种,可传入所有的参数,包含类型参数,明确的指定了T是string类型,并作为参数传给函数,使用<>进行处理,如下:

let output = identity<string>("myString");  // type of output will be 'string'

第二种,可使用类型推导,让编译器自动根据传递的参数类型确定具体的类型,此时没必须要使用<>来明确的传入类型,如下:

let output = identity("myString");  // type of output will be 'string'

1.泛型变量

  使用泛型创建泛型函数时,编译器要求函数体必须正确的使用,必须把参数当作任意或所有类型,示例如下:

function identity<T>(arg: T): T {
    return arg;
}

  如果想获取arg的长度,调用arg.length,编译器将会报错,因为没有地方指明arg具有length属性,此处的变量代表的时任意类型。也可以传入泛型指定的数组,如下:

function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

  也可以使用如下方式定义:

function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

2.泛型接口

  可使用V和W作为类型变量来表示任何字母都可以,名称没有具体的含义,JS中不支持泛型,所以在编译后无泛型相关的代码,泛型纯粹是为了编译时进行类型的校验,确保类型的安全抽象。使用方式:

interface Identities<V, W> {
   id1: V,
   id2: W
}

3.泛型类

  泛型类与泛型接口类型,使用<>指定泛型类型,跟在类名后面。使用如下:

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

  与接口一样,直接把泛型类型放到类的后面,可以帮助确认类的所有属性都在使用相同的类型。 注意:泛型指定的是类的实例部分,所有类的静态部分不能使用泛型类型。

4.泛型约束

  上述例子中使用参数arg的length属性时因为参数的类型时任意类型,所以会报错,这时可以使用泛型约束确保参数都包含length属性,这样既可以在函数中访问length属性了。示例如下:

interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

  此时泛型函数被定义了约束,因此它不再是可以使用任意类型:

loggingIdentity(3);  // Error, number doesn't have a .length property

  需要传入符合约束类型的值,必须包含需要的属性才可以:

loggingIdentity({length: 10, value: 3});

  可以声明一个类型参数,且它被另一个类型参数所约束,比如:

function find<T, U extends Findable<T>>(n: T, s: U) {
  // ...
}
find (giraffe, myAnimals);

  在 TypeScript 使用泛型创建工厂函数时,需要引用构造函数的类类型,比如:

function create<T>(c: {new(): T; }): T {
    return new c();
}

三、枚举

  使用枚举可以定义有名字的数字常量,通过enum关键字进行定义:

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}

1.枚举

  一个枚举类型可以包含零个或多个枚举成员,枚举成员具有一个数字值,可以是常数或计算得出的值,常数时需要满足一下条件:

  • 不具有初始化函数并且之前的枚举成员是常数,此时当前枚举成员的值为上一个枚举成员的值加1,第一个枚举元素的初始值是0。
  • 枚举成员使用常数枚举表达式初始化,常数枚举表达式是ts表达式的子集,可以在编译阶段求值,当满足以下条件时,就是一个常数枚举表达式:
    • 数字字面量
    • 引用之前定义的常数枚举成员,可以在不同的枚举类型中定义,如果成员是在同一个枚举类型中定义的,可以使用非限定名来引用
    • 带括号的常数枚举表达式
    • +, -, ~ 一元运算符应用于常数枚举表达式
    • +, -, *, /, %, <<, >>, >>>, &, |, ^ 二元运算符,常数枚举表达式做为其一个操作对象 若常数枚举表达式求值后为 NaN或Infinity,则会在编译阶段报错。 其它所有情况的枚举成员被当作是需要计算得出的值:
enum FileAccess {
    // constant members
    None,
    Read    = 1 << 1,
    Write   = 1 << 2,
    ReadWrite  = Read | Write
    // computed member
    G = "123".length
}

  枚举是在运行时真正存在的一个对象,可以从枚举值到枚举名进行反向映射,如下:

enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[Enum.A]; // "A"

  编译后,枚举类型被编译成一个对象,它包含双向映射(name->value)和(value->name)。引用枚举成员总会生成一次属性访问并且永远不会内联,当访问枚举值时为了避免生成多余的代码和间接引用,可以使用常数枚举,常数枚举使用const修饰符。

const enum Enum {
    A = 1,
    B = A * 2
}

  常数枚举只能使用常数枚举表达式并且不同于常规的枚举是它们在编译阶段会被删除,常数枚举成员在使用的地方被内联进来,是因为常数枚举不可能有计算成员。

const enum Directions {
    Up,
    Down,
    Left,
    Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]

  编译后代码为:

var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

2.外部枚举

  外部枚举用于描述已经存在的枚举类型的形状。

declare enum Enum {
    A = 1,
    B,
    C = 2
}

  外部枚举和非外部枚举之间有一个重要的区别,正常的枚举中,没有初始化方法的成员被当作常数成员,对于非常数的外部枚举而言,没有初始化方法时被当作需要经过计算的。

标签:function,return,及泛,TS,枚举,参数,let,card
From: https://blog.51cto.com/u_15518033/9286289

相关文章

  • P9017 [USACO23JAN] Lights Off G 题解
    一次操作相当于把\(a\)异或上\(b\),修改开关的一位相当于将这一位异或上\(1\)。会发现一个很神奇的性质:初始开关对灯的影响和改变开关状态对灯的影响是独立的。而前者的影响是固定的,所以我们可以只考虑改变开关状态对灯的影响。假设一共需要\(k\)次操作能使所有灯关闭,如果我......
  • 鸿蒙HarmonyO实战-ArkTS语言(状态管理)
    ......
  • 鸿蒙HarmonyOS实战-ArkTS语言(基本语法)
    ......
  • WhatsApp广播列表功能介绍及用法
    如果遇到想要发送一条信息给多个客户的时候,WhatsApp广播功能就能帮到你。WhatsApp的广播功能可以让你将同一条消息发送给多个联系人,而这些联系人不会知道你已向其他联系人发送了相同的消息。所以广播功能非常适合于一次向多个人发送通知或公告,例如线下活动通知、公司内部通知、最新......
  • 【6.0】socketserver实现并发
    【一】引入socket并不能多并发,只能支持一个用户socketserver模块是Python中用于创建网络服务器的模块,提供了一种简单而一致的接口。它是构建网络服务器的框架,处理了创建、维护和关闭连接的许多底层细节socketserver是socket的再封装。【二】socketserver介绍【1】简......
  • Gstreamer Rtspsrc连接大华摄像头失败原因及解决
    先说解决办法sudoapt-getremovegstreamer1.0-plugins-ugly分析过程和原因输入命令gst-launch-1.0rtspsrclocation="rtsp/url"!fakesink终端输出如下SettingpipelinetoPAUSED...PipelineisliveanddoesnotneedPREROLL...Progress:(open)OpeningStre......
  • 初次上手接触ArkTs
    本文分享自华为云社区《学习ArtTs--初见ArkTs》,作者:Uncle_Tom。1.前言需要静态分析去检查一个语言,必须对这个语言有深刻的认识,才能有效的对这个语言进行有效的检查。我常说:“作为一个程序分析员需要比一般的程序员考虑的更多。通常程序员只要考虑在需求和结果之间建立一条......
  • TS学习笔记三:接口及类
      本节介绍ts的接口及类相关内容,接口是ts中为类型或第三方代码定义契约,有时被称做“鸭式辨型法”或“结构性子类型化”。讲解视频:https://www.ixigua.com/7321247404299125282一、接口  Ts是需要对变量等指定类型并进行类型检查,定义方式如下:interfaceIn{a:string;}f......
  • FX110网:钱是拿不回来了!AUGS Markets平台早已关网停业
    如果还有投资者有在AUGSMarkets平台出金未到账的,只怕是永远也不会到账了,因为该平台早已停业,网站也已无法打开。近期,一汇友向我站投诉,称其一年多前在AUGSMarkets平台的出金申请当时就已通过,可至今还有部分资金未到账。因为等待时间太久,汇友早已对出金不抱什么希望了,但还是心有不......
  • echarts 中国地图南海诸岛展示为简图
    echarts官网,地理位置/地图(https://echarts.apache.org/examples/zh/index.html#chart-type-map)基于“香港”示例,进行中国地图的展示1、下载中国地图的json文件,并引用https://datav.aliyun.com/portal/school/atlas/area_selectorimportmapDatafrom'@/utils/chinaMap.json......